Bitcoin¶
Import thư viện¶
In [1]:
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from keras.models import Sequential
from keras.layers import SimpleRNN, Dense, Dropout
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ReduceLROnPlateau
from keras import regularizers
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
Bitcoin Dataset¶
Import csv¶
In [2]:
# Đọc file Bitcoin
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Bitcoin Historical Data.csv"
data = pd.read_csv(file_path)
# Loại bỏ dấu phẩy và chuyển đổi thành float
for col in ['Price', 'Open']:
data[col] = data[col].str.replace(',', '', regex=False).astype(float)
# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
val = str(val).replace(',', '').strip()
if 'K' in val:
return float(val.replace('K', '')) * 1_000
elif 'M' in val:
return float(val.replace('M', '')) * 1_000_000
elif 'B' in val:
return float(val.replace('B', '')) * 1_000_000_000
else:
return float(val)
data['Vol.'] = data['Vol.'].apply(convert_volume)
# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)
# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']
First 5 rows:
Price Open Vol.
Date
2016-03-10 415.8 412.8 55740.0
2016-03-11 419.1 415.8 60630.0
2016-03-12 410.4 419.1 59640.0
2016-03-13 412.4 410.4 34980.0
2016-03-14 414.3 412.4 49330.0
Tổng số dữ liệu: 3370 dòng
Chia 7:3¶
Chuẩn hóa dữ liệu¶
In [3]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [4]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data) * 0.7)
train_data = data.iloc[0:train_size,:]
test_data = data.iloc[train_size:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input = scaled_input[0:train_size,:]
scaled_test_input = scaled_input[train_size:,:]
scaled_train_target = scaled_target[0:train_size,:]
scaled_test_target = scaled_target[train_size:,:]
print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
Kích thước tập train: 2359 Kích thước tập test: 1011
Xây dựng mô hình RNN¶
In [5]:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
Out[5]:
'\nXây dựng mô hình RNN với regularization\n\nArgs:\n time_step: Số time steps để nhìn về quá khứ\n num_features: Số features đầu vào (Price, Open, Vol = 3)\n\nReturns:\n Sequential model\n'
In [6]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [7]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [8]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình¶
In [9]:
# Tạo dữ liệu train và test với time_step = 50
time_step = 50
X_train, y_train = create_multivariate_time_series_data(scaled_train_input, scaled_train_target, time_step)
X_test, y_test = create_multivariate_time_series_data(scaled_test_input, scaled_test_target, time_step)
print(f"X_train shape: {X_train.shape}")
print(f"y_train shape: {y_train.shape}")
print(f"X_test shape: {X_test.shape}")
print(f"y_test shape: {y_test.shape}")
# Xây dựng mô hình RNN
model_rnn = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình
history_rnn = model_rnn.fit(
X_train, y_train,
epochs=60,
batch_size=16,
validation_data=(X_test, y_test),
callbacks=[early_stop, reduce_lr],
verbose=1
)
X_train shape: (2309, 50, 3) y_train shape: (2309,) X_test shape: (961, 50, 3) y_test shape: (961,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
145/145 ━━━━━━━━━━━━━━━━━━━━ 3s 8ms/step - loss: 0.0712 - mae: 0.1481 - val_loss: 0.0605 - val_mae: 0.1607 - learning_rate: 1.0000e-04 Epoch 2/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0157 - mae: 0.0581 - val_loss: 0.0232 - val_mae: 0.0811 - learning_rate: 1.0000e-04 Epoch 3/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0122 - mae: 0.0465 - val_loss: 0.0198 - val_mae: 0.0839 - learning_rate: 1.0000e-04 Epoch 4/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0110 - mae: 0.0440 - val_loss: 0.0151 - val_mae: 0.0677 - learning_rate: 1.0000e-04 Epoch 5/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0384 - val_loss: 0.0157 - val_mae: 0.0713 - learning_rate: 1.0000e-04 Epoch 6/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0092 - mae: 0.0383 - val_loss: 0.0116 - val_mae: 0.0515 - learning_rate: 1.0000e-04 Epoch 7/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0087 - mae: 0.0344 - val_loss: 0.0087 - val_mae: 0.0388 - learning_rate: 1.0000e-04 Epoch 8/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0085 - mae: 0.0353 - val_loss: 0.0082 - val_mae: 0.0366 - learning_rate: 1.0000e-04 Epoch 9/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0084 - mae: 0.0341 - val_loss: 0.0081 - val_mae: 0.0375 - learning_rate: 1.0000e-04 Epoch 10/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0079 - mae: 0.0321 - val_loss: 0.0063 - val_mae: 0.0266 - learning_rate: 1.0000e-04 Epoch 11/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0075 - mae: 0.0306 - val_loss: 0.0062 - val_mae: 0.0277 - learning_rate: 1.0000e-04 Epoch 12/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0077 - mae: 0.0305 - val_loss: 0.0083 - val_mae: 0.0486 - learning_rate: 1.0000e-04 Epoch 13/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0266 - val_loss: 0.0062 - val_mae: 0.0274 - learning_rate: 1.0000e-04 Epoch 14/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0276 - val_loss: 0.0055 - val_mae: 0.0250 - learning_rate: 1.0000e-04 Epoch 15/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0065 - mae: 0.0265 - val_loss: 0.0058 - val_mae: 0.0282 - learning_rate: 1.0000e-04 Epoch 16/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0283 - val_loss: 0.0056 - val_mae: 0.0263 - learning_rate: 1.0000e-04 Epoch 17/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0065 - mae: 0.0270 - val_loss: 0.0059 - val_mae: 0.0327 - learning_rate: 1.0000e-04 Epoch 18/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0062 - mae: 0.0259 - val_loss: 0.0053 - val_mae: 0.0244 - learning_rate: 1.0000e-04 Epoch 19/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0063 - mae: 0.0269 - val_loss: 0.0052 - val_mae: 0.0233 - learning_rate: 1.0000e-04 Epoch 20/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0263 - val_loss: 0.0048 - val_mae: 0.0220 - learning_rate: 1.0000e-04 Epoch 21/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0060 - mae: 0.0258 - val_loss: 0.0046 - val_mae: 0.0192 - learning_rate: 1.0000e-04 Epoch 22/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0058 - mae: 0.0260 - val_loss: 0.0043 - val_mae: 0.0161 - learning_rate: 1.0000e-04 Epoch 23/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0055 - mae: 0.0250 - val_loss: 0.0051 - val_mae: 0.0307 - learning_rate: 1.0000e-04 Epoch 24/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0053 - mae: 0.0240 - val_loss: 0.0046 - val_mae: 0.0251 - learning_rate: 1.0000e-04 Epoch 25/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0054 - mae: 0.0253 - val_loss: 0.0045 - val_mae: 0.0227 - learning_rate: 1.0000e-04 Epoch 26/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0052 - mae: 0.0251 - val_loss: 0.0043 - val_mae: 0.0204 - learning_rate: 1.0000e-04 Epoch 27/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0223 - val_loss: 0.0045 - val_mae: 0.0247 - learning_rate: 1.0000e-04 Epoch 28/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0049 - mae: 0.0233 - val_loss: 0.0053 - val_mae: 0.0351 - learning_rate: 1.0000e-04 Epoch 29/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0233 - val_loss: 0.0052 - val_mae: 0.0357 - learning_rate: 1.0000e-04 Epoch 30/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0046 - mae: 0.0227 - val_loss: 0.0041 - val_mae: 0.0225 - learning_rate: 1.0000e-04 Epoch 31/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0047 - mae: 0.0233 - val_loss: 0.0038 - val_mae: 0.0188 - learning_rate: 1.0000e-04 Epoch 32/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0044 - mae: 0.0228 - val_loss: 0.0035 - val_mae: 0.0144 - learning_rate: 1.0000e-04 Epoch 33/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0044 - mae: 0.0233 - val_loss: 0.0035 - val_mae: 0.0154 - learning_rate: 1.0000e-04 Epoch 34/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0042 - mae: 0.0214 - val_loss: 0.0033 - val_mae: 0.0142 - learning_rate: 1.0000e-04 Epoch 35/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0043 - mae: 0.0235 - val_loss: 0.0034 - val_mae: 0.0163 - learning_rate: 1.0000e-04 Epoch 36/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0041 - mae: 0.0220 - val_loss: 0.0032 - val_mae: 0.0150 - learning_rate: 1.0000e-04 Epoch 37/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0040 - mae: 0.0221 - val_loss: 0.0031 - val_mae: 0.0133 - learning_rate: 1.0000e-04 Epoch 38/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0040 - mae: 0.0226 - val_loss: 0.0031 - val_mae: 0.0139 - learning_rate: 1.0000e-04 Epoch 39/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0213 - val_loss: 0.0032 - val_mae: 0.0167 - learning_rate: 1.0000e-04 Epoch 40/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0216 - val_loss: 0.0032 - val_mae: 0.0173 - learning_rate: 1.0000e-04 Epoch 41/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0038 - mae: 0.0219 - val_loss: 0.0031 - val_mae: 0.0174 - learning_rate: 1.0000e-04 Epoch 42/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0036 - mae: 0.0207 - val_loss: 0.0039 - val_mae: 0.0301 - learning_rate: 1.0000e-04 Epoch 43/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0037 - mae: 0.0223 - val_loss: 0.0030 - val_mae: 0.0181 - learning_rate: 1.0000e-04 Epoch 44/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0034 - mae: 0.0213 - val_loss: 0.0028 - val_mae: 0.0157 - learning_rate: 1.0000e-04 Epoch 45/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0032 - mae: 0.0204 - val_loss: 0.0027 - val_mae: 0.0150 - learning_rate: 1.0000e-04 Epoch 46/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0032 - mae: 0.0204 - val_loss: 0.0025 - val_mae: 0.0127 - learning_rate: 1.0000e-04 Epoch 47/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0033 - mae: 0.0215 - val_loss: 0.0034 - val_mae: 0.0271 - learning_rate: 1.0000e-04 Epoch 48/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0208 - val_loss: 0.0024 - val_mae: 0.0137 - learning_rate: 1.0000e-04 Epoch 49/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0213 - val_loss: 0.0027 - val_mae: 0.0191 - learning_rate: 1.0000e-04 Epoch 50/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0030 - mae: 0.0207 - val_loss: 0.0030 - val_mae: 0.0247 - learning_rate: 1.0000e-04 Epoch 51/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0027 - mae: 0.0190 - val_loss: 0.0022 - val_mae: 0.0138 - learning_rate: 1.0000e-04 Epoch 52/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0028 - mae: 0.0197 - val_loss: 0.0022 - val_mae: 0.0143 - learning_rate: 1.0000e-04 Epoch 53/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0194 - val_loss: 0.0028 - val_mae: 0.0229 - learning_rate: 1.0000e-04 Epoch 54/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0193 - val_loss: 0.0034 - val_mae: 0.0312 - learning_rate: 1.0000e-04 Epoch 55/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0025 - mae: 0.0189 - val_loss: 0.0028 - val_mae: 0.0247 - learning_rate: 1.0000e-04 Epoch 56/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0026 - mae: 0.0195 - val_loss: 0.0024 - val_mae: 0.0199 - learning_rate: 1.0000e-04 Epoch 57/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0024 - mae: 0.0186 - val_loss: 0.0022 - val_mae: 0.0175 - learning_rate: 1.0000e-04 Epoch 58/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0026 - mae: 0.0201 - val_loss: 0.0026 - val_mae: 0.0226 - learning_rate: 1.0000e-04 Epoch 59/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0023 - mae: 0.0187 - val_loss: 0.0024 - val_mae: 0.0219 - learning_rate: 1.0000e-04 Epoch 60/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0187 - val_loss: 0.0018 - val_mae: 0.0143 - learning_rate: 1.0000e-04 Restoring model weights from the end of the best epoch: 60.
Đánh giá mô hình¶
In [10]:
# Vẽ val_loss để đánh giá overfitting
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất
best_epoch = np.argmin(history_rnn.history['val_loss']) + 1
best_val_loss = min(history_rnn.history['val_loss'])
print(f"Epoch tốt nhất: {best_epoch} với val_loss: {best_val_loss:.6f}")
Epoch tốt nhất: 60 với val_loss: 0.001849
Dự đoán và trực quan hóa¶
In [11]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_data_scaled = scaled_test_input[-time_step:]
forecasted_prices_30 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 30, scaler_target)
forecasted_prices_60 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 60, scaler_target)
forecasted_prices_90 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra
test_predict_scaled = model_rnn.predict(X_test)
test_predict_rnn = scaler_target.inverse_transform(test_predict_scaled)
# Tạo DataFrame cho các dự đoán
forecast_dates_30 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30 = pd.DataFrame(forecasted_prices_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecasted_prices_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecasted_prices_90, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa kết quả
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test
plt.plot(test_data.index[time_step:], test_predict_rnn,
label='Dự đoán trên tập test', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
label='Dự đoán 30 ngày tiếp theo', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'],
label='Dự đoán 60 ngày tiếp theo', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'],
label='Dự đoán 90 ngày tiếp theo', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá Bitcoin bằng RNN (Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
31/31 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
In [12]:
# Đánh giá mô hình
# Lấy giá trị thực tế trên tập test
y_test_actual = test_data['Price'].values[time_step:]
# Tính toán các metrics
mape = mean_absolute_percentage_error(y_test_actual, test_predict_rnn.flatten())
mse = mean_squared_error(y_test_actual, test_predict_rnn.flatten())
rmse = np.sqrt(mse)
print(f'Kết quả đánh giá mô hình RNN (Time Step = {time_step}):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo:')
print(f'Giá cao nhất: ${forecasted_prices_30.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30.mean():.2f}')
Kết quả đánh giá mô hình RNN (Time Step = 50): MAPE: 0.03% MSE: 5527311.55 RMSE: 2351.02 Số epochs huấn luyện: 60 Dự đoán giá Bitcoin 30 ngày tiếp theo: Giá cao nhất: $101692.81 Giá thấp nhất: $87836.89 Giá trung bình: $92697.28
Chia 8:2¶
Chuẩn hóa dữ liệu 8:2¶
In [13]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [14]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data) * 0.8)
train_data_82 = data.iloc[0:train_size_82,:]
test_data_82 = data.iloc[train_size_82:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input_82 = scaled_input[0:train_size_82,:]
scaled_test_input_82 = scaled_input[train_size_82:,:]
scaled_train_target_82 = scaled_target[0:train_size_82,:]
scaled_test_target_82 = scaled_target[train_size_82:,:]
print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2696 Kích thước tập test 8:2: 674
Xây dựng mô hình RNN¶
In [15]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [16]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [17]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình 8:2¶
In [18]:
# Tạo dữ liệu train và test với time_step = 50 cho split 8:2
X_train_82, y_train_82 = create_multivariate_time_series_data(scaled_train_input_82, scaled_train_target_82, time_step)
X_test_82, y_test_82 = create_multivariate_time_series_data(scaled_test_input_82, scaled_test_target_82, time_step)
print(f"X_train_82 shape: {X_train_82.shape}")
print(f"y_train_82 shape: {y_train_82.shape}")
print(f"X_test_82 shape: {X_test_82.shape}")
print(f"y_test_82 shape: {y_test_82.shape}")
# Xây dựng mô hình RNN cho split 8:2
model_rnn_82 = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop_82 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_82 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình 8:2
history_rnn_82 = model_rnn_82.fit(
X_train_82, y_train_82,
epochs=60,
batch_size=16,
validation_data=(X_test_82, y_test_82),
callbacks=[early_stop_82, reduce_lr_82],
verbose=1
)
X_train_82 shape: (2646, 50, 3) y_train_82 shape: (2646,) X_test_82 shape: (624, 50, 3) y_test_82 shape: (624,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
166/166 ━━━━━━━━━━━━━━━━━━━━ 3s 9ms/step - loss: 0.0558 - mae: 0.1317 - val_loss: 0.1150 - val_mae: 0.2632 - learning_rate: 1.0000e-04 Epoch 2/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0131 - mae: 0.0506 - val_loss: 0.0607 - val_mae: 0.1839 - learning_rate: 1.0000e-04 Epoch 3/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0111 - mae: 0.0423 - val_loss: 0.0459 - val_mae: 0.1529 - learning_rate: 1.0000e-04 Epoch 4/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0107 - mae: 0.0403 - val_loss: 0.0398 - val_mae: 0.1531 - learning_rate: 1.0000e-04 Epoch 5/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0107 - mae: 0.0409 - val_loss: 0.0321 - val_mae: 0.1316 - learning_rate: 1.0000e-04 Epoch 6/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0346 - val_loss: 0.0253 - val_mae: 0.1144 - learning_rate: 1.0000e-04 Epoch 7/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0089 - mae: 0.0338 - val_loss: 0.0218 - val_mae: 0.1075 - learning_rate: 1.0000e-04 Epoch 8/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0328 - val_loss: 0.0228 - val_mae: 0.1007 - learning_rate: 1.0000e-04 Epoch 9/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0083 - mae: 0.0300 - val_loss: 0.0216 - val_mae: 0.1018 - learning_rate: 1.0000e-04 Epoch 10/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0083 - mae: 0.0314 - val_loss: 0.0155 - val_mae: 0.0806 - learning_rate: 1.0000e-04 Epoch 11/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0081 - mae: 0.0300 - val_loss: 0.0138 - val_mae: 0.0698 - learning_rate: 1.0000e-04 Epoch 12/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0073 - mae: 0.0273 - val_loss: 0.0137 - val_mae: 0.0711 - learning_rate: 1.0000e-04 Epoch 13/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0075 - mae: 0.0284 - val_loss: 0.0135 - val_mae: 0.0774 - learning_rate: 1.0000e-04 Epoch 14/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0074 - mae: 0.0282 - val_loss: 0.0118 - val_mae: 0.0669 - learning_rate: 1.0000e-04 Epoch 15/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0268 - val_loss: 0.0096 - val_mae: 0.0511 - learning_rate: 1.0000e-04 Epoch 16/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0065 - mae: 0.0244 - val_loss: 0.0127 - val_mae: 0.0764 - learning_rate: 1.0000e-04 Epoch 17/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0255 - val_loss: 0.0101 - val_mae: 0.0603 - learning_rate: 1.0000e-04 Epoch 18/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0248 - val_loss: 0.0113 - val_mae: 0.0704 - learning_rate: 1.0000e-04 Epoch 19/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0061 - mae: 0.0246 - val_loss: 0.0083 - val_mae: 0.0508 - learning_rate: 1.0000e-04 Epoch 20/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0239 - val_loss: 0.0105 - val_mae: 0.0684 - learning_rate: 1.0000e-04 Epoch 21/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0057 - mae: 0.0240 - val_loss: 0.0074 - val_mae: 0.0450 - learning_rate: 1.0000e-04 Epoch 22/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0055 - mae: 0.0229 - val_loss: 0.0061 - val_mae: 0.0334 - learning_rate: 1.0000e-04 Epoch 23/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0237 - val_loss: 0.0073 - val_mae: 0.0478 - learning_rate: 1.0000e-04 Epoch 24/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0229 - val_loss: 0.0090 - val_mae: 0.0621 - learning_rate: 1.0000e-04 Epoch 25/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0052 - mae: 0.0226 - val_loss: 0.0074 - val_mae: 0.0504 - learning_rate: 1.0000e-04 Epoch 26/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0051 - mae: 0.0227 - val_loss: 0.0068 - val_mae: 0.0438 - learning_rate: 1.0000e-04 Epoch 27/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0217 - val_loss: 0.0056 - val_mae: 0.0333 - learning_rate: 1.0000e-04 Epoch 28/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0223 - val_loss: 0.0078 - val_mae: 0.0553 - learning_rate: 1.0000e-04 Epoch 29/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0048 - mae: 0.0224 - val_loss: 0.0083 - val_mae: 0.0590 - learning_rate: 1.0000e-04 Epoch 30/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0045 - mae: 0.0215 - val_loss: 0.0062 - val_mae: 0.0423 - learning_rate: 1.0000e-04 Epoch 31/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0202 - val_loss: 0.0060 - val_mae: 0.0429 - learning_rate: 1.0000e-04 Epoch 32/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0043 - mae: 0.0209 - val_loss: 0.0083 - val_mae: 0.0629 - learning_rate: 1.0000e-04 Epoch 33/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0221 - val_loss: 0.0062 - val_mae: 0.0479 - learning_rate: 1.0000e-04 Epoch 34/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0219 - val_loss: 0.0063 - val_mae: 0.0507 - learning_rate: 1.0000e-04 Epoch 35/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0204 - val_loss: 0.0061 - val_mae: 0.0480 - learning_rate: 1.0000e-04 Epoch 36/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0208 - val_loss: 0.0048 - val_mae: 0.0374 - learning_rate: 1.0000e-04 Epoch 37/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0196 - val_loss: 0.0047 - val_mae: 0.0353 - learning_rate: 1.0000e-04 Epoch 38/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0035 - mae: 0.0196 - val_loss: 0.0056 - val_mae: 0.0474 - learning_rate: 1.0000e-04 Epoch 39/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0213 - val_loss: 0.0049 - val_mae: 0.0405 - learning_rate: 1.0000e-04 Epoch 40/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0034 - mae: 0.0199 - val_loss: 0.0076 - val_mae: 0.0642 - learning_rate: 1.0000e-04 Epoch 41/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0193 - val_loss: 0.0053 - val_mae: 0.0465 - learning_rate: 1.0000e-04 Epoch 42/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0201 - val_loss: 0.0052 - val_mae: 0.0463 - learning_rate: 1.0000e-04 Epoch 43/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0031 - mae: 0.0195 - val_loss: 0.0037 - val_mae: 0.0319 - learning_rate: 1.0000e-04 Epoch 44/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0030 - mae: 0.0195 - val_loss: 0.0052 - val_mae: 0.0481 - learning_rate: 1.0000e-04 Epoch 45/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0029 - mae: 0.0192 - val_loss: 0.0044 - val_mae: 0.0420 - learning_rate: 1.0000e-04 Epoch 46/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0187 - val_loss: 0.0037 - val_mae: 0.0337 - learning_rate: 1.0000e-04 Epoch 47/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0028 - mae: 0.0200 - val_loss: 0.0050 - val_mae: 0.0480 - learning_rate: 1.0000e-04 Epoch 48/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0027 - mae: 0.0197 - val_loss: 0.0042 - val_mae: 0.0417 - learning_rate: 1.0000e-04 Epoch 49/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0026 - mae: 0.0197 - val_loss: 0.0040 - val_mae: 0.0396 - learning_rate: 1.0000e-04 Epoch 50/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0025 - mae: 0.0195 - val_loss: 0.0028 - val_mae: 0.0261 - learning_rate: 1.0000e-04 Epoch 51/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0026 - mae: 0.0195 - val_loss: 0.0031 - val_mae: 0.0315 - learning_rate: 1.0000e-04 Epoch 52/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0022 - mae: 0.0182 - val_loss: 0.0030 - val_mae: 0.0315 - learning_rate: 1.0000e-04 Epoch 53/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0187 - val_loss: 0.0024 - val_mae: 0.0229 - learning_rate: 1.0000e-04 Epoch 54/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0197 - val_loss: 0.0043 - val_mae: 0.0455 - learning_rate: 1.0000e-04 Epoch 55/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0021 - mae: 0.0178 - val_loss: 0.0026 - val_mae: 0.0282 - learning_rate: 1.0000e-04 Epoch 56/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0188 - val_loss: 0.0018 - val_mae: 0.0167 - learning_rate: 1.0000e-04 Epoch 57/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0020 - mae: 0.0188 - val_loss: 0.0022 - val_mae: 0.0250 - learning_rate: 1.0000e-04 Epoch 58/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0192 - val_loss: 0.0026 - val_mae: 0.0299 - learning_rate: 1.0000e-04 Epoch 59/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0020 - mae: 0.0191 - val_loss: 0.0021 - val_mae: 0.0239 - learning_rate: 1.0000e-04 Epoch 60/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0020 - mae: 0.0195 - val_loss: 0.0034 - val_mae: 0.0413 - learning_rate: 1.0000e-04 Restoring model weights from the end of the best epoch: 56.
Đánh giá mô hình 8:2¶
In [19]:
# Vẽ val_loss để đánh giá overfitting cho split 8:2
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 8:2 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất cho split 8:2
best_epoch_82 = np.argmin(history_rnn_82.history['val_loss']) + 1
best_val_loss_82 = min(history_rnn_82.history['val_loss'])
print(f"Epoch tốt nhất (8:2): {best_epoch_82} với val_loss: {best_val_loss_82:.6f}")
Epoch tốt nhất (8:2): 56 với val_loss: 0.001776
Dự đoán và trực quan hóa 8:2¶
In [20]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 8:2
last_data_scaled_82 = scaled_test_input_82[-time_step:]
forecasted_prices_30_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 30, scaler_target)
forecasted_prices_60_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 60, scaler_target)
forecasted_prices_90_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra cho split 8:2
test_predict_scaled_82 = model_rnn_82.predict(X_test_82)
test_predict_rnn_82 = scaler_target.inverse_transform(test_predict_scaled_82)
# Tạo DataFrame cho các dự đoán 8:2
forecast_dates_30_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30_82 = pd.DataFrame(forecasted_prices_30_82, index=forecast_dates_30_82, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecasted_prices_60_82, index=forecast_dates_60_82, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecasted_prices_90_82, index=forecast_dates_90_82, columns=['Price'])
# Trực quan hóa kết quả cho split 8:2
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test 8:2
plt.plot(test_data_82.index[time_step:], test_predict_rnn_82,
label='Dự đoán trên tập test (8:2)', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai 8:2
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
label='Dự đoán 30 ngày tiếp theo (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
label='Dự đoán 60 ngày tiếp theo (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
label='Dự đoán 90 ngày tiếp theo (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá Bitcoin bằng RNN (8:2 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
20/20 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
In [21]:
# Đánh giá mô hình 8:2
# Lấy giá trị thực tế trên tập test 8:2
y_test_actual_82 = test_data_82['Price'].values[time_step:]
# Tính toán các metrics cho split 8:2
mape_82 = mean_absolute_percentage_error(y_test_actual_82, test_predict_rnn_82.flatten())
mse_82 = mean_squared_error(y_test_actual_82, test_predict_rnn_82.flatten())
rmse_82 = np.sqrt(mse_82)
print(f'Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_82.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày cho 8:2
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (8:2):')
print(f'Giá cao nhất: ${forecasted_prices_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_82.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_82.mean():.2f}')
Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = 50): MAPE: 0.03% MSE: 6470138.82 RMSE: 2543.65 Số epochs huấn luyện: 60 Dự đoán giá Bitcoin 30 ngày tiếp theo (8:2): Giá cao nhất: $103510.48 Giá thấp nhất: $101639.00 Giá trung bình: $102173.93
Chia 9:1¶
Chuẩn hóa dữ liệu 9:1¶
In [22]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [23]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data) * 0.9)
train_data_91 = data.iloc[0:train_size_91,:]
test_data_91 = data.iloc[train_size_91:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input_91 = scaled_input[0:train_size_91,:]
scaled_test_input_91 = scaled_input[train_size_91:,:]
scaled_train_target_91 = scaled_target[0:train_size_91,:]
scaled_test_target_91 = scaled_target[train_size_91:,:]
print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3033 Kích thước tập test 9:1: 337
Xây dựng mô hình RNN¶
In [24]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [25]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [26]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình 9:1¶
In [27]:
# Tạo dữ liệu train và test với time_step = 50 cho split 9:1
X_train_91, y_train_91 = create_multivariate_time_series_data(scaled_train_input_91, scaled_train_target_91, time_step)
X_test_91, y_test_91 = create_multivariate_time_series_data(scaled_test_input_91, scaled_test_target_91, time_step)
print(f"X_train_91 shape: {X_train_91.shape}")
print(f"y_train_91 shape: {y_train_91.shape}")
print(f"X_test_91 shape: {X_test_91.shape}")
print(f"y_test_91 shape: {y_test_91.shape}")
# Xây dựng mô hình RNN cho split 9:1
model_rnn_91 = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop_91 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_91 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình 9:1
history_rnn_91 = model_rnn_91.fit(
X_train_91, y_train_91,
epochs=60,
batch_size=16,
validation_data=(X_test_91, y_test_91),
callbacks=[early_stop_91, reduce_lr_91],
verbose=1
)
X_train_91 shape: (2983, 50, 3) y_train_91 shape: (2983,) X_test_91 shape: (287, 50, 3) y_test_91 shape: (287,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
187/187 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - loss: 0.0316 - mae: 0.0923 - val_loss: 0.0410 - val_mae: 0.1767 - learning_rate: 1.0000e-04 Epoch 2/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0135 - mae: 0.0525 - val_loss: 0.0303 - val_mae: 0.1472 - learning_rate: 1.0000e-04 Epoch 3/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0124 - mae: 0.0485 - val_loss: 0.0157 - val_mae: 0.0901 - learning_rate: 1.0000e-04 Epoch 4/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0104 - mae: 0.0421 - val_loss: 0.0172 - val_mae: 0.0979 - learning_rate: 1.0000e-04 Epoch 5/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0093 - mae: 0.0383 - val_loss: 0.0131 - val_mae: 0.0776 - learning_rate: 1.0000e-04 Epoch 6/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0088 - mae: 0.0353 - val_loss: 0.0119 - val_mae: 0.0729 - learning_rate: 1.0000e-04 Epoch 7/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0086 - mae: 0.0348 - val_loss: 0.0104 - val_mae: 0.0647 - learning_rate: 1.0000e-04 Epoch 8/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0082 - mae: 0.0335 - val_loss: 0.0101 - val_mae: 0.0646 - learning_rate: 1.0000e-04 Epoch 9/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0075 - mae: 0.0315 - val_loss: 0.0116 - val_mae: 0.0752 - learning_rate: 1.0000e-04 Epoch 10/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0073 - mae: 0.0305 - val_loss: 0.0076 - val_mae: 0.0444 - learning_rate: 1.0000e-04 Epoch 11/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0074 - mae: 0.0317 - val_loss: 0.0073 - val_mae: 0.0444 - learning_rate: 1.0000e-04 Epoch 12/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0289 - val_loss: 0.0075 - val_mae: 0.0481 - learning_rate: 1.0000e-04 Epoch 13/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0290 - val_loss: 0.0070 - val_mae: 0.0442 - learning_rate: 1.0000e-04 Epoch 14/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0065 - mae: 0.0289 - val_loss: 0.0081 - val_mae: 0.0548 - learning_rate: 1.0000e-04 Epoch 15/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0062 - mae: 0.0276 - val_loss: 0.0056 - val_mae: 0.0302 - learning_rate: 1.0000e-04 Epoch 16/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0063 - mae: 0.0286 - val_loss: 0.0054 - val_mae: 0.0296 - learning_rate: 1.0000e-04 Epoch 17/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0277 - val_loss: 0.0061 - val_mae: 0.0386 - learning_rate: 1.0000e-04 Epoch 18/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0283 - val_loss: 0.0054 - val_mae: 0.0329 - learning_rate: 1.0000e-04 Epoch 19/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0058 - mae: 0.0271 - val_loss: 0.0059 - val_mae: 0.0404 - learning_rate: 1.0000e-04 Epoch 20/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0055 - mae: 0.0260 - val_loss: 0.0060 - val_mae: 0.0418 - learning_rate: 1.0000e-04 Epoch 21/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0259 - val_loss: 0.0053 - val_mae: 0.0356 - learning_rate: 1.0000e-04 Epoch 22/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0051 - mae: 0.0249 - val_loss: 0.0042 - val_mae: 0.0215 - learning_rate: 1.0000e-04 Epoch 23/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0052 - mae: 0.0267 - val_loss: 0.0047 - val_mae: 0.0302 - learning_rate: 1.0000e-04 Epoch 24/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0047 - mae: 0.0239 - val_loss: 0.0044 - val_mae: 0.0283 - learning_rate: 1.0000e-04 Epoch 25/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0242 - val_loss: 0.0045 - val_mae: 0.0305 - learning_rate: 1.0000e-04 Epoch 26/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0257 - val_loss: 0.0046 - val_mae: 0.0335 - learning_rate: 1.0000e-04 Epoch 27/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0252 - val_loss: 0.0045 - val_mae: 0.0337 - learning_rate: 1.0000e-04 Epoch 28/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0242 - val_loss: 0.0044 - val_mae: 0.0333 - learning_rate: 1.0000e-04 Epoch 29/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0234 - val_loss: 0.0042 - val_mae: 0.0318 - learning_rate: 1.0000e-04 Epoch 30/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0226 - val_loss: 0.0037 - val_mae: 0.0259 - learning_rate: 1.0000e-04 Epoch 31/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0040 - mae: 0.0240 - val_loss: 0.0056 - val_mae: 0.0495 - learning_rate: 1.0000e-04 Epoch 32/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0225 - val_loss: 0.0035 - val_mae: 0.0255 - learning_rate: 1.0000e-04 Epoch 33/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0036 - mae: 0.0220 - val_loss: 0.0056 - val_mae: 0.0511 - learning_rate: 1.0000e-04 Epoch 34/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0036 - mae: 0.0227 - val_loss: 0.0036 - val_mae: 0.0293 - learning_rate: 1.0000e-04 Epoch 35/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0241 - val_loss: 0.0044 - val_mae: 0.0413 - learning_rate: 1.0000e-04 Epoch 36/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0035 - mae: 0.0230 - val_loss: 0.0030 - val_mae: 0.0224 - learning_rate: 1.0000e-04 Epoch 37/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0224 - val_loss: 0.0051 - val_mae: 0.0493 - learning_rate: 1.0000e-04 Epoch 38/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0033 - mae: 0.0230 - val_loss: 0.0026 - val_mae: 0.0189 - learning_rate: 1.0000e-04 Epoch 39/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0030 - mae: 0.0214 - val_loss: 0.0062 - val_mae: 0.0603 - learning_rate: 1.0000e-04 Epoch 40/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0230 - val_loss: 0.0027 - val_mae: 0.0234 - learning_rate: 1.0000e-04 Epoch 41/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0029 - mae: 0.0218 - val_loss: 0.0041 - val_mae: 0.0419 - learning_rate: 1.0000e-04 Epoch 42/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0029 - mae: 0.0222 - val_loss: 0.0049 - val_mae: 0.0504 - learning_rate: 1.0000e-04 Epoch 43/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0028 - mae: 0.0218 - val_loss: 0.0036 - val_mae: 0.0375 - learning_rate: 1.0000e-04 Epoch 44/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0029 - mae: 0.0224 - val_loss: 0.0030 - val_mae: 0.0313 - learning_rate: 1.0000e-04 Epoch 45/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0027 - mae: 0.0218 - val_loss: 0.0033 - val_mae: 0.0352 - learning_rate: 1.0000e-04 Epoch 46/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0203 - val_loss: 0.0036 - val_mae: 0.0400 - learning_rate: 1.0000e-04 Epoch 47/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0221 - val_loss: 0.0041 - val_mae: 0.0452 - learning_rate: 1.0000e-04 Epoch 48/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0025 - mae: 0.0216 Epoch 48: ReduceLROnPlateau reducing learning rate to 4.999999873689376e-05. 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0216 - val_loss: 0.0038 - val_mae: 0.0430 - learning_rate: 1.0000e-04 Epoch 49/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0225 - val_loss: 0.0034 - val_mae: 0.0383 - learning_rate: 5.0000e-05 Epoch 50/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0215 - val_loss: 0.0044 - val_mae: 0.0495 - learning_rate: 5.0000e-05 Epoch 51/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0209 - val_loss: 0.0035 - val_mae: 0.0405 - learning_rate: 5.0000e-05 Epoch 52/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0208 - val_loss: 0.0030 - val_mae: 0.0351 - learning_rate: 5.0000e-05 Epoch 53/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0214 - val_loss: 0.0033 - val_mae: 0.0393 - learning_rate: 5.0000e-05 Epoch 53: early stopping Restoring model weights from the end of the best epoch: 38.
Đánh giá mô hình 9:1¶
In [28]:
# Vẽ val_loss để đánh giá overfitting cho split 9:1
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 9:1 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất cho split 9:1
best_epoch_91 = np.argmin(history_rnn_91.history['val_loss']) + 1
best_val_loss_91 = min(history_rnn_91.history['val_loss'])
print(f"Epoch tốt nhất (9:1): {best_epoch_91} với val_loss: {best_val_loss_91:.6f}")
Epoch tốt nhất (9:1): 38 với val_loss: 0.002644
Dự đoán và trực quan hóa 9:1¶
In [29]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 9:1
last_data_scaled_91 = scaled_test_input_91[-time_step:]
forecasted_prices_30_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 30, scaler_target)
forecasted_prices_60_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 60, scaler_target)
forecasted_prices_90_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra cho split 9:1
test_predict_scaled_91 = model_rnn_91.predict(X_test_91)
test_predict_rnn_91 = scaler_target.inverse_transform(test_predict_scaled_91)
# Tạo DataFrame cho các dự đoán 9:1
forecast_dates_30_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30_91 = pd.DataFrame(forecasted_prices_30_91, index=forecast_dates_30_91, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecasted_prices_60_91, index=forecast_dates_60_91, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecasted_prices_90_91, index=forecast_dates_90_91, columns=['Price'])
# Trực quan hóa kết quả cho split 9:1
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test 9:1
plt.plot(test_data_91.index[time_step:], test_predict_rnn_91,
label='Dự đoán trên tập test (9:1)', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai 9:1
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
label='Dự đoán 30 ngày tiếp theo (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
label='Dự đoán 60 ngày tiếp theo (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
label='Dự đoán 90 ngày tiếp theo (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá Bitcoin bằng RNN (9:1 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step
In [30]:
# Đánh giá mô hình 9:1
# Lấy giá trị thực tế trên tập test 9:1
y_test_actual_91 = test_data_91['Price'].values[time_step:]
# Tính toán các metrics cho split 9:1
mape_91 = mean_absolute_percentage_error(y_test_actual_91, test_predict_rnn_91.flatten())
mse_91 = mean_squared_error(y_test_actual_91, test_predict_rnn_91.flatten())
rmse_91 = np.sqrt(mse_91)
print(f'Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_91.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày cho 9:1
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (9:1):')
print(f'Giá cao nhất: ${forecasted_prices_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_91.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_91.mean():.2f}')
Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = 50): MAPE: 0.02% MSE: 7844551.54 RMSE: 2800.81 Số epochs huấn luyện: 53 Dự đoán giá Bitcoin 30 ngày tiếp theo (9:1): Giá cao nhất: $103320.61 Giá thấp nhất: $68552.92 Giá trung bình: $85429.66
So sánh 3 tỉ lệ¶
In [31]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU")
print("="*80)
# Thu thập thông tin từ 3 splits
splits_info = {
'7:3': {
'train_size': len(train_data),
'test_size': len(test_data),
'mape': mape,
'mse': mse,
'rmse': rmse,
'epochs': len(history_rnn.history['loss']),
'best_val_loss': best_val_loss,
'best_epoch': best_epoch,
'final_train_loss': history_rnn.history['loss'][-1],
'final_val_loss': history_rnn.history['val_loss'][-1]
},
'8:2': {
'train_size': len(train_data_82),
'test_size': len(test_data_82),
'mape': mape_82,
'mse': mse_82,
'rmse': rmse_82,
'epochs': len(history_rnn_82.history['loss']),
'best_val_loss': best_val_loss_82,
'best_epoch': best_epoch_82,
'final_train_loss': history_rnn_82.history['loss'][-1],
'final_val_loss': history_rnn_82.history['val_loss'][-1]
},
'9:1': {
'train_size': len(train_data_91),
'test_size': len(test_data_91),
'mape': mape_91,
'mse': mse_91,
'rmse': rmse_91,
'epochs': len(history_rnn_91.history['loss']),
'best_val_loss': best_val_loss_91,
'best_epoch': best_epoch_91,
'final_train_loss': history_rnn_91.history['loss'][-1],
'final_val_loss': history_rnn_91.history['val_loss'][-1]
}
}
# In bảng so sánh
for split, info in splits_info.items():
print(f"\n{split} Split:")
print(f" Kích thước train: {info['train_size']:,} mẫu")
print(f" Kích thước test: {info['test_size']:,} mẫu")
print(f" MAPE: {info['mape']:.2f}%")
print(f" MSE: {info['mse']:,.2f}")
print(f" RMSE: {info['rmse']:,.2f}")
print(f" Số epochs: {info['epochs']}")
print(f" Best val_loss: {info['best_val_loss']:.6f} (epoch {info['best_epoch']})")
print(f" Final train_loss: {info['final_train_loss']:.6f}")
print(f" Final val_loss: {info['final_val_loss']:.6f}")
print(f" Overfitting gap: {abs(info['final_val_loss'] - info['final_train_loss']):.6f}")
================================================================================ SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU ================================================================================ 7:3 Split: Kích thước train: 2,359 mẫu Kích thước test: 1,011 mẫu MAPE: 0.03% MSE: 5,527,311.55 RMSE: 2,351.02 Số epochs: 60 Best val_loss: 0.001849 (epoch 60) Final train_loss: 0.002247 Final val_loss: 0.001849 Overfitting gap: 0.000399 8:2 Split: Kích thước train: 2,696 mẫu Kích thước test: 674 mẫu MAPE: 0.03% MSE: 6,470,138.82 RMSE: 2,543.65 Số epochs: 60 Best val_loss: 0.001776 (epoch 56) Final train_loss: 0.001935 Final val_loss: 0.003447 Overfitting gap: 0.001512 9:1 Split: Kích thước train: 3,033 mẫu Kích thước test: 337 mẫu MAPE: 0.02% MSE: 7,844,551.54 RMSE: 2,800.81 Số epochs: 53 Best val_loss: 0.002644 (epoch 38) Final train_loss: 0.002328 Final val_loss: 0.003336 Overfitting gap: 0.001008
In [32]:
# Vẽ biểu đồ so sánh các metrics
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
# 1. So sánh MAPE
mape_values = [splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 2. So sánh RMSE
rmse_values = [splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')
# 3. So sánh Best Validation Loss
best_val_loss_values = [splits_info[split]['best_val_loss'] for split in splits]
axes[0, 2].bar(splits, best_val_loss_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh Best Validation Loss', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('Best Val Loss')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(best_val_loss_values):
axes[0, 2].text(i, v + 0.0001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')
# 4. So sánh số epochs
epochs_values = [splits_info[split]['epochs'] for split in splits]
axes[1, 0].bar(splits, epochs_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh Số Epochs', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('Số Epochs')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(epochs_values):
axes[1, 0].text(i, v + 0.5, f'{v}', ha='center', va='bottom', fontweight='bold')
# 5. So sánh Overfitting Gap
overfitting_gap = [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits]
axes[1, 1].bar(splits, overfitting_gap, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Overfitting Gap', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('|Val Loss - Train Loss|')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(overfitting_gap):
axes[1, 1].text(i, v + 0.001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')
# 6. So sánh kích thước test set
test_sizes = [splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')
plt.tight_layout()
plt.show()
C:\Users\Hii\AppData\Local\Temp\ipykernel_28116\2283599594.py:62: UserWarning: Tight layout not applied. The bottom and top margins cannot be made large enough to accommodate all Axes decorations. plt.tight_layout()
In [33]:
# Vẽ so sánh Training và Validation Loss curves
plt.figure(figsize=(18, 6))
# Subplot 1: 7:3 Split
plt.subplot(1, 3, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('7:3 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch}')
# Subplot 2: 8:2 Split
plt.subplot(1, 3, 2)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('8:2 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_82-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_82}')
# Subplot 3: 9:1 Split
plt.subplot(1, 3, 3)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('9:1 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_91-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_91}')
plt.tight_layout()
plt.show()
In [34]:
# Tạo DataFrame tổng hợp kết quả để dễ so sánh
import pandas as pd
comparison_df = pd.DataFrame({
'Split': ['7:3', '8:2', '9:1'],
'Train_Size': [splits_info[split]['train_size'] for split in splits],
'Test_Size': [splits_info[split]['test_size'] for split in splits],
'MAPE (%)': [splits_info[split]['mape'] for split in splits],
'RMSE (USD)': [splits_info[split]['rmse'] for split in splits],
'MSE': [splits_info[split]['mse'] for split in splits],
'Best_Val_Loss': [splits_info[split]['best_val_loss'] for split in splits],
'Best_Epoch': [splits_info[split]['best_epoch'] for split in splits],
'Total_Epochs': [splits_info[split]['epochs'] for split in splits],
'Overfitting_Gap': [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits]
})
print("\nBẢNG TỔNG HỢP KẾT QUẢ:")
print("="*100)
print(comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ: ==================================================================================================== Split Train_Size Test_Size MAPE (%) RMSE (USD) MSE Best_Val_Loss Best_Epoch Total_Epochs Overfitting_Gap 7:3 2359 1011 0.0297 2351.0235 5527311.5494 0.0018 60 60 0.0004 8:2 2696 674 0.0275 2543.6468 6470138.8172 0.0018 56 60 0.0015 9:1 3033 337 0.0248 2800.8127 7844551.5441 0.0026 38 53 0.0010
In [35]:
# Phân tích và đưa ra khuyến nghị
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ")
print("="*80)
# Tìm split tốt nhất cho từng metric
best_mape_split = splits[np.argmin([splits_info[split]['mape'] for split in splits])]
best_rmse_split = splits[np.argmin([splits_info[split]['rmse'] for split in splits])]
best_val_loss_split = splits[np.argmin([splits_info[split]['best_val_loss'] for split in splits])]
best_overfitting_split = splits[np.argmin([abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits])]
print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:")
print(f" • Tốt nhất theo MAPE: {best_mape_split} ({splits_info[best_mape_split]['mape']:.2f}%)")
print(f" • Tốt nhất theo RMSE: {best_rmse_split} ({splits_info[best_rmse_split]['rmse']:,.2f} USD)")
print(f" • Tốt nhất theo Val Loss: {best_val_loss_split} ({splits_info[best_val_loss_split]['best_val_loss']:.6f})")
print(f" • Ít overfitting nhất: {best_overfitting_split} (gap: {abs(splits_info[best_overfitting_split]['final_val_loss'] - splits_info[best_overfitting_split]['final_train_loss']):.6f})")
# Tính điểm tổng hợp (rank-based scoring)
def calculate_rank_score(splits_info):
scores = {}
splits_list = list(splits_info.keys())
# Rank cho MAPE (thấp hơn = tốt hơn)
mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
# Rank cho RMSE (thấp hơn = tốt hơn)
rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
# Rank cho Best Val Loss (thấp hơn = tốt hơn)
val_loss_rank = sorted(splits_list, key=lambda x: splits_info[x]['best_val_loss'])
# Rank cho Overfitting Gap (thấp hơn = tốt hơn)
overfitting_rank = sorted(splits_list, key=lambda x: abs(splits_info[x]['final_val_loss'] - splits_info[x]['final_train_loss']))
for split in splits_list:
# Điểm rank (1 = tốt nhất, 3 = kém nhất)
score = (mape_rank.index(split) + 1) * 0.3 + \
(rmse_rank.index(split) + 1) * 0.3 + \
(val_loss_rank.index(split) + 1) * 0.25 + \
(overfitting_rank.index(split) + 1) * 0.15
scores[split] = score
return scores
# Tính điểm tổng hợp
scores = calculate_rank_score(splits_info)
best_overall_split = min(scores, key=scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):")
for split in splits_info.keys():
print(f" • {split}: {scores[split]:.2f} điểm")
print(f" • {split}: {scores[split]:.2f} điểm")
print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ:")
print(f" 🏆 MÔ HÌNH TỐT NHẤT: Split {best_overall_split}")
print(f" 📊 Lý do:")
print(f" - MAPE: {splits_info[best_overall_split]['mape']:.2f}%")
print(f" - RMSE: {splits_info[best_overall_split]['rmse']:,.2f} USD")
print(f" - Best Val Loss: {splits_info[best_overall_split]['best_val_loss']:.6f}")
print(f" - Overfitting Gap: {abs(splits_info[best_overall_split]['final_val_loss'] - splits_info[best_overall_split]['final_train_loss']):.6f}")
print(f" - Tập test có {splits_info[best_overall_split]['test_size']:,} mẫu (đủ để đánh giá)")
print(f" - Huấn luyện ổn định với {splits_info[best_overall_split]['epochs']} epochs")
print(f"\n4. NHẬN XÉT CHUNG:")
if best_overall_split == '7:3':
print(" • Split 7:3 cân bằng tốt giữa kích thước tập train và test")
print(" • Tập test đủ lớn để đánh giá độ tin cậy của mô hình")
print(" • Hiệu suất dự đoán tốt với mức overfitting chấp nhận được")
elif best_overall_split == '8:2':
print(" • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn")
print(" • Tập test vẫn đủ lớn để đánh giá")
print(" • Cân bằng tốt giữa hiệu suất và độ tin cậy")
else: # 9:1
print(" • Split 9:1 tối đa hóa dữ liệu train")
print(" • Có thể có rủi ro về độ tin cậy do tập test nhỏ")
print(" • Phù hợp khi có ít dữ liệu và cần tối ưu hiệu suất")
print(f"\n ⚠️ LƯU Ý: Với dữ liệu time series như Bitcoin, nên chọn split cân bằng")
print(f" để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ
================================================================================
1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:
• Tốt nhất theo MAPE: 9:1 (0.02%)
• Tốt nhất theo RMSE: 7:3 (2,351.02 USD)
• Tốt nhất theo Val Loss: 8:2 (0.001776)
• Ít overfitting nhất: 7:3 (gap: 0.000399)
2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):
• 7:3: 1.85 điểm
• 7:3: 1.85 điểm
• 8:2: 1.90 điểm
• 8:2: 1.90 điểm
• 9:1: 2.25 điểm
• 9:1: 2.25 điểm
3. KẾT LUẬN VÀ KHUYẾN NGHỊ:
🏆 MÔ HÌNH TỐT NHẤT: Split 7:3
📊 Lý do:
- MAPE: 0.03%
- RMSE: 2,351.02 USD
- Best Val Loss: 0.001849
- Overfitting Gap: 0.000399
- Tập test có 1,011 mẫu (đủ để đánh giá)
- Huấn luyện ổn định với 60 epochs
4. NHẬN XÉT CHUNG:
• Split 7:3 cân bằng tốt giữa kích thước tập train và test
• Tập test đủ lớn để đánh giá độ tin cậy của mô hình
• Hiệu suất dự đoán tốt với mức overfitting chấp nhận được
⚠️ LƯU Ý: Với dữ liệu time series như Bitcoin, nên chọn split cân bằng
để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.
In [36]:
# Vẽ biểu đồ radar cho so sánh tổng thể
import numpy as np
import matplotlib.pyplot as plt
def create_radar_chart():
# Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
metrics = ['MAPE', 'RMSE', 'Val_Loss', 'Overfitting', 'Test_Size']
# Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
data = {}
for split in splits:
mape_norm = 1 - (splits_info[split]['mape'] - min([splits_info[s]['mape'] for s in splits])) / \
(max([splits_info[s]['mape'] for s in splits]) - min([splits_info[s]['mape'] for s in splits]))
rmse_norm = 1 - (splits_info[split]['rmse'] - min([splits_info[s]['rmse'] for s in splits])) / \
(max([splits_info[s]['rmse'] for s in splits]) - min([splits_info[s]['rmse'] for s in splits]))
val_loss_norm = 1 - (splits_info[split]['best_val_loss'] - min([splits_info[s]['best_val_loss'] for s in splits])) / \
(max([splits_info[s]['best_val_loss'] for s in splits]) - min([splits_info[s]['best_val_loss'] for s in splits]))
overfitting_gaps = [abs(splits_info[s]['final_val_loss'] - splits_info[s]['final_train_loss']) for s in splits]
overfitting_norm = 1 - (abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) - min(overfitting_gaps)) / \
(max(overfitting_gaps) - min(overfitting_gaps))
test_size_norm = (splits_info[split]['test_size'] - min([splits_info[s]['test_size'] for s in splits])) / \
(max([splits_info[s]['test_size'] for s in splits]) - min([splits_info[s]['test_size'] for s in splits]))
data[split] = [mape_norm, rmse_norm, val_loss_norm, overfitting_norm, test_size_norm]
# Tạo radar chart
angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
angles += angles[:1] # Complete the circle
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
for i, (split, values) in enumerate(data.items()):
values += values[:1] # Complete the circle
ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
ax.fill(angles, values, alpha=0.25, color=colors[i])
ax.set_xticks(angles[:-1])
ax.set_xticklabels(metrics)
ax.set_ylim(0, 1)
ax.set_title('So sánh tổng thể các Split (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
ax.grid(True)
plt.tight_layout()
plt.show()
create_radar_chart()
ETH¶
Import thư viện¶
In [37]:
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from keras.models import Sequential
from keras.layers import SimpleRNN, Dense, Dropout
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ReduceLROnPlateau
from keras import regularizers
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
ETH Dataset¶
Import csv¶
In [38]:
# Đọc file ETH
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Ethereum Historical Data.csv"
data = pd.read_csv(file_path)
# Loại bỏ dấu phẩy và chuyển đổi thành float cho Price và Open
for col in ['Price', 'Open']:
data[col] = data[col].str.replace(',', '', regex=False).astype(float)
# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B' thành số thực
def convert_volume(val):
val = str(val).replace(',', '').strip()
if 'K' in val:
return float(val.replace('K', '')) * 1_000
elif 'M' in val:
return float(val.replace('M', '')) * 1_000_000
elif 'B' in val:
return float(val.replace('B', '')) * 1_000_000_000
else:
try:
return float(val)
except ValueError:
return np.nan # Trường hợp val là '' hoặc không chuyển được
data['Vol.'] = data['Vol.'].apply(convert_volume)
# Kiểm tra NaN ban đầu trong Vol.
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Nội suy giá trị Vol. (chỉ sau khi đã convert sang số)
data['Vol.'] = data['Vol.'].interpolate(mdataod='linear')
# Điền 0 cho NaN còn lại
data['Vol.'] = data['Vol.'].fillna(0)
# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)
# Thông tin dữ liệu
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 8
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']
First 5 rows:
Price Open Vol.
Date
2016-03-10 11.75 11.20 0.0
2016-03-11 11.95 11.75 180.0
2016-03-12 12.92 11.95 830.0
2016-03-13 15.07 12.92 1300.0
2016-03-14 12.50 15.07 92180.0
Tổng số dữ liệu: 3370 dòng
Chia 7:3¶
Chuẩn hóa dữ liệu¶
In [39]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [40]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data) * 0.7)
train_data = data.iloc[0:train_size,:]
test_data = data.iloc[train_size:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input = scaled_input[0:train_size,:]
scaled_test_input = scaled_input[train_size:,:]
scaled_train_target = scaled_target[0:train_size,:]
scaled_test_target = scaled_target[train_size:,:]
print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
Kích thước tập train: 2359 Kích thước tập test: 1011
Xây dựng mô hình RNN¶
In [41]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [42]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [43]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình¶
In [44]:
# Tạo dữ liệu train và test với time_step = 50
time_step = 50
X_train, y_train = create_multivariate_time_series_data(scaled_train_input, scaled_train_target, time_step)
X_test, y_test = create_multivariate_time_series_data(scaled_test_input, scaled_test_target, time_step)
print(f"X_train shape: {X_train.shape}")
print(f"y_train shape: {y_train.shape}")
print(f"X_test shape: {X_test.shape}")
print(f"y_test shape: {y_test.shape}")
# Xây dựng mô hình RNN
model_rnn = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình
history_rnn = model_rnn.fit(
X_train, y_train,
epochs=60,
batch_size=16,
validation_data=(X_test, y_test),
callbacks=[early_stop, reduce_lr],
verbose=1
)
X_train shape: (2309, 50, 3) y_train shape: (2309,) X_test shape: (961, 50, 3) y_test shape: (961,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
145/145 ━━━━━━━━━━━━━━━━━━━━ 3s 8ms/step - loss: 0.0858 - mae: 0.1458 - val_loss: 0.0196 - val_mae: 0.0830 - learning_rate: 1.0000e-04 Epoch 2/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0251 - mae: 0.0710 - val_loss: 0.0117 - val_mae: 0.0588 - learning_rate: 1.0000e-04 Epoch 3/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0183 - mae: 0.0615 - val_loss: 0.0110 - val_mae: 0.0625 - learning_rate: 1.0000e-04 Epoch 4/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0166 - mae: 0.0527 - val_loss: 0.0093 - val_mae: 0.0445 - learning_rate: 1.0000e-04 Epoch 5/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0139 - mae: 0.0496 - val_loss: 0.0092 - val_mae: 0.0456 - learning_rate: 1.0000e-04 Epoch 6/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0132 - mae: 0.0461 - val_loss: 0.0075 - val_mae: 0.0347 - learning_rate: 1.0000e-04 Epoch 7/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0127 - mae: 0.0462 - val_loss: 0.0089 - val_mae: 0.0487 - learning_rate: 1.0000e-04 Epoch 8/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0129 - mae: 0.0463 - val_loss: 0.0069 - val_mae: 0.0301 - learning_rate: 1.0000e-04 Epoch 9/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0111 - mae: 0.0405 - val_loss: 0.0085 - val_mae: 0.0475 - learning_rate: 1.0000e-04 Epoch 10/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0122 - mae: 0.0443 - val_loss: 0.0085 - val_mae: 0.0500 - learning_rate: 1.0000e-04 Epoch 11/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0110 - mae: 0.0407 - val_loss: 0.0062 - val_mae: 0.0248 - learning_rate: 1.0000e-04 Epoch 12/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0100 - mae: 0.0387 - val_loss: 0.0083 - val_mae: 0.0495 - learning_rate: 1.0000e-04 Epoch 13/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0361 - val_loss: 0.0079 - val_mae: 0.0466 - learning_rate: 1.0000e-04 Epoch 14/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0090 - mae: 0.0340 - val_loss: 0.0084 - val_mae: 0.0513 - learning_rate: 1.0000e-04 Epoch 15/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0094 - mae: 0.0373 - val_loss: 0.0067 - val_mae: 0.0348 - learning_rate: 1.0000e-04 Epoch 16/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0089 - mae: 0.0357 - val_loss: 0.0058 - val_mae: 0.0243 - learning_rate: 1.0000e-04 Epoch 17/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0097 - mae: 0.0379 - val_loss: 0.0079 - val_mae: 0.0481 - learning_rate: 1.0000e-04 Epoch 18/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0085 - mae: 0.0349 - val_loss: 0.0095 - val_mae: 0.0635 - learning_rate: 1.0000e-04 Epoch 19/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0083 - mae: 0.0335 - val_loss: 0.0062 - val_mae: 0.0326 - learning_rate: 1.0000e-04 Epoch 20/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0085 - mae: 0.0340 - val_loss: 0.0076 - val_mae: 0.0502 - learning_rate: 1.0000e-04 Epoch 21/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0080 - mae: 0.0333 - val_loss: 0.0077 - val_mae: 0.0509 - learning_rate: 1.0000e-04 Epoch 22/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0082 - mae: 0.0334 - val_loss: 0.0065 - val_mae: 0.0386 - learning_rate: 1.0000e-04 Epoch 23/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0082 - mae: 0.0344 - val_loss: 0.0072 - val_mae: 0.0473 - learning_rate: 1.0000e-04 Epoch 24/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0077 - mae: 0.0328 - val_loss: 0.0063 - val_mae: 0.0377 - learning_rate: 1.0000e-04 Epoch 25/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0077 - mae: 0.0324 - val_loss: 0.0061 - val_mae: 0.0359 - learning_rate: 1.0000e-04 Epoch 26/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0314 - val_loss: 0.0055 - val_mae: 0.0290 - learning_rate: 1.0000e-04 Epoch 27/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0312 - val_loss: 0.0061 - val_mae: 0.0374 - learning_rate: 1.0000e-04 Epoch 28/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0069 - mae: 0.0299 - val_loss: 0.0054 - val_mae: 0.0292 - learning_rate: 1.0000e-04 Epoch 29/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0068 - mae: 0.0297 - val_loss: 0.0071 - val_mae: 0.0486 - learning_rate: 1.0000e-04 Epoch 30/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0301 - val_loss: 0.0071 - val_mae: 0.0486 - learning_rate: 1.0000e-04 Epoch 31/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0305 - val_loss: 0.0073 - val_mae: 0.0507 - learning_rate: 1.0000e-04 Epoch 32/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0071 - mae: 0.0307 - val_loss: 0.0061 - val_mae: 0.0403 - learning_rate: 1.0000e-04 Epoch 33/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0311 - val_loss: 0.0066 - val_mae: 0.0459 - learning_rate: 1.0000e-04 Epoch 34/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0066 - mae: 0.0299 - val_loss: 0.0056 - val_mae: 0.0349 - learning_rate: 1.0000e-04 Epoch 35/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0301 - val_loss: 0.0046 - val_mae: 0.0226 - learning_rate: 1.0000e-04 Epoch 36/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0290 - val_loss: 0.0071 - val_mae: 0.0524 - learning_rate: 1.0000e-04 Epoch 37/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0062 - mae: 0.0291 - val_loss: 0.0052 - val_mae: 0.0334 - learning_rate: 1.0000e-04 Epoch 38/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0287 - val_loss: 0.0052 - val_mae: 0.0339 - learning_rate: 1.0000e-04 Epoch 39/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0286 - val_loss: 0.0057 - val_mae: 0.0384 - learning_rate: 1.0000e-04 Epoch 40/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0066 - mae: 0.0300 - val_loss: 0.0048 - val_mae: 0.0287 - learning_rate: 1.0000e-04 Epoch 41/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0295 - val_loss: 0.0049 - val_mae: 0.0327 - learning_rate: 1.0000e-04 Epoch 42/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0059 - mae: 0.0286 - val_loss: 0.0046 - val_mae: 0.0279 - learning_rate: 1.0000e-04 Epoch 43/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0056 - mae: 0.0281 - val_loss: 0.0047 - val_mae: 0.0296 - learning_rate: 1.0000e-04 Epoch 44/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0302 - val_loss: 0.0062 - val_mae: 0.0473 - learning_rate: 1.0000e-04 Epoch 45/60 135/145 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0053 - mae: 0.0268 Epoch 45: ReduceLROnPlateau reducing learning rate to 4.999999873689376e-05. 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0054 - mae: 0.0269 - val_loss: 0.0051 - val_mae: 0.0363 - learning_rate: 1.0000e-04 Epoch 46/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0060 - mae: 0.0298 - val_loss: 0.0053 - val_mae: 0.0388 - learning_rate: 5.0000e-05 Epoch 47/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0057 - mae: 0.0279 - val_loss: 0.0049 - val_mae: 0.0343 - learning_rate: 5.0000e-05 Epoch 48/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0308 - val_loss: 0.0047 - val_mae: 0.0314 - learning_rate: 5.0000e-05 Epoch 49/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0286 - val_loss: 0.0050 - val_mae: 0.0362 - learning_rate: 5.0000e-05 Epoch 50/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0288 - val_loss: 0.0047 - val_mae: 0.0327 - learning_rate: 5.0000e-05 Epoch 51/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0281 - val_loss: 0.0043 - val_mae: 0.0273 - learning_rate: 5.0000e-05 Epoch 52/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0053 - mae: 0.0275 - val_loss: 0.0050 - val_mae: 0.0369 - learning_rate: 5.0000e-05 Epoch 53/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0280 - val_loss: 0.0045 - val_mae: 0.0308 - learning_rate: 5.0000e-05 Epoch 54/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0306 - val_loss: 0.0042 - val_mae: 0.0269 - learning_rate: 5.0000e-05 Epoch 55/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0058 - mae: 0.0288 - val_loss: 0.0044 - val_mae: 0.0301 - learning_rate: 5.0000e-05 Epoch 56/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0054 - mae: 0.0282 - val_loss: 0.0040 - val_mae: 0.0243 - learning_rate: 5.0000e-05 Epoch 57/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0057 - mae: 0.0292 - val_loss: 0.0038 - val_mae: 0.0211 - learning_rate: 5.0000e-05 Epoch 58/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0293 - val_loss: 0.0036 - val_mae: 0.0181 - learning_rate: 5.0000e-05 Epoch 59/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0053 - mae: 0.0278 - val_loss: 0.0038 - val_mae: 0.0235 - learning_rate: 5.0000e-05 Epoch 60/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0050 - mae: 0.0271 - val_loss: 0.0037 - val_mae: 0.0216 - learning_rate: 5.0000e-05 Restoring model weights from the end of the best epoch: 58.
Đánh giá mô hình¶
In [45]:
# Vẽ val_loss để đánh giá overfitting
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất
best_epoch = np.argmin(history_rnn.history['val_loss']) + 1
best_val_loss = min(history_rnn.history['val_loss'])
print(f"Epoch tốt nhất: {best_epoch} với val_loss: {best_val_loss:.6f}")
Epoch tốt nhất: 58 với val_loss: 0.003579
Dự đoán và trực quan hóa¶
In [46]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_data_scaled = scaled_test_input[-time_step:]
forecasted_prices_30 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 30, scaler_target)
forecasted_prices_60 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 60, scaler_target)
forecasted_prices_90 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra
test_predict_scaled = model_rnn.predict(X_test)
test_predict_rnn = scaler_target.inverse_transform(test_predict_scaled)
# Tạo DataFrame cho các dự đoán
forecast_dates_30 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30 = pd.DataFrame(forecasted_prices_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecasted_prices_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecasted_prices_90, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa kết quả
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test
plt.plot(test_data.index[time_step:], test_predict_rnn,
label='Dự đoán trên tập test', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
label='Dự đoán 30 ngày tiếp theo', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'],
label='Dự đoán 60 ngày tiếp theo', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'],
label='Dự đoán 90 ngày tiếp theo', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá ETH bằng RNN (Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
31/31 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
In [47]:
# Đánh giá mô hình
# Lấy giá trị thực tế trên tập test
y_test_actual = test_data['Price'].values[time_step:]
# Tính toán các metrics
mape = mean_absolute_percentage_error(y_test_actual, test_predict_rnn.flatten())
mse = mean_squared_error(y_test_actual, test_predict_rnn.flatten())
rmse = np.sqrt(mse)
print(f'Kết quả đánh giá mô hình RNN (Time Step = {time_step}):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày
print(f'\nDự đoán giá ETH 30 ngày tiếp theo:')
print(f'Giá cao nhất: ${forecasted_prices_30.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30.mean():.2f}')
Kết quả đánh giá mô hình RNN (Time Step = 50): MAPE: 0.03% MSE: 15641.58 RMSE: 125.07 Số epochs huấn luyện: 60 Dự đoán giá ETH 30 ngày tiếp theo: Giá cao nhất: $2527.08 Giá thấp nhất: $2330.73 Giá trung bình: $2425.73
Chia 8:2¶
Chuẩn hóa dữ liệu 8:2¶
In [48]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [49]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data) * 0.8)
train_data_82 = data.iloc[0:train_size_82,:]
test_data_82 = data.iloc[train_size_82:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input_82 = scaled_input[0:train_size_82,:]
scaled_test_input_82 = scaled_input[train_size_82:,:]
scaled_train_target_82 = scaled_target[0:train_size_82,:]
scaled_test_target_82 = scaled_target[train_size_82:,:]
print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2696 Kích thước tập test 8:2: 674
Xây dựng mô hình RNN¶
In [50]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [51]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [52]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình 8:2¶
In [53]:
# Tạo dữ liệu train và test với time_step = 50 cho split 8:2
X_train_82, y_train_82 = create_multivariate_time_series_data(scaled_train_input_82, scaled_train_target_82, time_step)
X_test_82, y_test_82 = create_multivariate_time_series_data(scaled_test_input_82, scaled_test_target_82, time_step)
print(f"X_train_82 shape: {X_train_82.shape}")
print(f"y_train_82 shape: {y_train_82.shape}")
print(f"X_test_82 shape: {X_test_82.shape}")
print(f"y_test_82 shape: {y_test_82.shape}")
# Xây dựng mô hình RNN cho split 8:2
model_rnn_82 = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop_82 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_82 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình 8:2
history_rnn_82 = model_rnn_82.fit(
X_train_82, y_train_82,
epochs=60,
batch_size=16,
validation_data=(X_test_82, y_test_82),
callbacks=[early_stop_82, reduce_lr_82],
verbose=1
)
X_train_82 shape: (2646, 50, 3) y_train_82 shape: (2646,) X_test_82 shape: (624, 50, 3) y_test_82 shape: (624,)
Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
166/166 ━━━━━━━━━━━━━━━━━━━━ 4s 10ms/step - loss: 0.0906 - mae: 0.1641 - val_loss: 0.0163 - val_mae: 0.0852 - learning_rate: 1.0000e-04 Epoch 2/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 3s 12ms/step - loss: 0.0234 - mae: 0.0757 - val_loss: 0.0125 - val_mae: 0.0727 - learning_rate: 1.0000e-04 Epoch 3/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.0172 - mae: 0.0607 - val_loss: 0.0142 - val_mae: 0.0856 - learning_rate: 1.0000e-04 Epoch 4/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0148 - mae: 0.0529 - val_loss: 0.0144 - val_mae: 0.0859 - learning_rate: 1.0000e-04 Epoch 5/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0134 - mae: 0.0511 - val_loss: 0.0166 - val_mae: 0.0983 - learning_rate: 1.0000e-04 Epoch 6/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0127 - mae: 0.0483 - val_loss: 0.0124 - val_mae: 0.0761 - learning_rate: 1.0000e-04 Epoch 7/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0120 - mae: 0.0456 - val_loss: 0.0118 - val_mae: 0.0728 - learning_rate: 1.0000e-04 Epoch 8/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0131 - mae: 0.0478 - val_loss: 0.0143 - val_mae: 0.0882 - learning_rate: 1.0000e-04 Epoch 9/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0113 - mae: 0.0420 - val_loss: 0.0110 - val_mae: 0.0686 - learning_rate: 1.0000e-04 Epoch 10/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0111 - mae: 0.0432 - val_loss: 0.0122 - val_mae: 0.0761 - learning_rate: 1.0000e-04 Epoch 11/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0103 - mae: 0.0398 - val_loss: 0.0091 - val_mae: 0.0555 - learning_rate: 1.0000e-04 Epoch 12/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0100 - mae: 0.0402 - val_loss: 0.0088 - val_mae: 0.0534 - learning_rate: 1.0000e-04 Epoch 13/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0093 - mae: 0.0381 - val_loss: 0.0102 - val_mae: 0.0652 - learning_rate: 1.0000e-04 Epoch 14/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0376 - val_loss: 0.0109 - val_mae: 0.0708 - learning_rate: 1.0000e-04 Epoch 15/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0091 - mae: 0.0365 - val_loss: 0.0098 - val_mae: 0.0622 - learning_rate: 1.0000e-04 Epoch 16/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0085 - mae: 0.0346 - val_loss: 0.0085 - val_mae: 0.0534 - learning_rate: 1.0000e-04 Epoch 17/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0089 - mae: 0.0358 - val_loss: 0.0078 - val_mae: 0.0486 - learning_rate: 1.0000e-04 Epoch 18/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0332 - val_loss: 0.0103 - val_mae: 0.0684 - learning_rate: 1.0000e-04 Epoch 19/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0080 - mae: 0.0343 - val_loss: 0.0090 - val_mae: 0.0600 - learning_rate: 1.0000e-04 Epoch 20/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0082 - mae: 0.0351 - val_loss: 0.0070 - val_mae: 0.0432 - learning_rate: 1.0000e-04 Epoch 21/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0078 - mae: 0.0327 - val_loss: 0.0081 - val_mae: 0.0547 - learning_rate: 1.0000e-04 Epoch 22/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0076 - mae: 0.0333 - val_loss: 0.0064 - val_mae: 0.0394 - learning_rate: 1.0000e-04 Epoch 23/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0324 - val_loss: 0.0085 - val_mae: 0.0585 - learning_rate: 1.0000e-04 Epoch 24/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0318 - val_loss: 0.0065 - val_mae: 0.0409 - learning_rate: 1.0000e-04 Epoch 25/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0351 - val_loss: 0.0067 - val_mae: 0.0450 - learning_rate: 1.0000e-04 Epoch 26/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0313 - val_loss: 0.0062 - val_mae: 0.0395 - learning_rate: 1.0000e-04 Epoch 27/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0073 - mae: 0.0329 - val_loss: 0.0065 - val_mae: 0.0448 - learning_rate: 1.0000e-04 Epoch 28/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0071 - mae: 0.0325 - val_loss: 0.0055 - val_mae: 0.0344 - learning_rate: 1.0000e-04 Epoch 29/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0065 - mae: 0.0307 - val_loss: 0.0053 - val_mae: 0.0324 - learning_rate: 1.0000e-04 Epoch 30/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0067 - mae: 0.0322 - val_loss: 0.0056 - val_mae: 0.0371 - learning_rate: 1.0000e-04 Epoch 31/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0313 - val_loss: 0.0063 - val_mae: 0.0447 - learning_rate: 1.0000e-04 Epoch 32/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0060 - mae: 0.0296 - val_loss: 0.0053 - val_mae: 0.0356 - learning_rate: 1.0000e-04 Epoch 33/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0304 - val_loss: 0.0056 - val_mae: 0.0381 - learning_rate: 1.0000e-04 Epoch 34/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0061 - mae: 0.0310 - val_loss: 0.0051 - val_mae: 0.0338 - learning_rate: 1.0000e-04 Epoch 35/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0063 - mae: 0.0318 - val_loss: 0.0075 - val_mae: 0.0593 - learning_rate: 1.0000e-04 Epoch 36/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0061 - mae: 0.0314 - val_loss: 0.0051 - val_mae: 0.0371 - learning_rate: 1.0000e-04 Epoch 37/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0057 - mae: 0.0298 - val_loss: 0.0044 - val_mae: 0.0281 - learning_rate: 1.0000e-04 Epoch 38/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0058 - mae: 0.0305 - val_loss: 0.0055 - val_mae: 0.0431 - learning_rate: 1.0000e-04 Epoch 39/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0053 - mae: 0.0285 - val_loss: 0.0048 - val_mae: 0.0356 - learning_rate: 1.0000e-04 Epoch 40/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0052 - mae: 0.0287 - val_loss: 0.0055 - val_mae: 0.0439 - learning_rate: 1.0000e-04 Epoch 41/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0053 - mae: 0.0295 - val_loss: 0.0048 - val_mae: 0.0371 - learning_rate: 1.0000e-04 Epoch 42/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0275 - val_loss: 0.0037 - val_mae: 0.0216 - learning_rate: 1.0000e-04 Epoch 43/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0052 - mae: 0.0295 - val_loss: 0.0037 - val_mae: 0.0227 - learning_rate: 1.0000e-04 Epoch 44/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0283 - val_loss: 0.0054 - val_mae: 0.0450 - learning_rate: 1.0000e-04 Epoch 45/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0047 - mae: 0.0271 - val_loss: 0.0042 - val_mae: 0.0309 - learning_rate: 1.0000e-04 Epoch 46/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0288 - val_loss: 0.0035 - val_mae: 0.0225 - learning_rate: 1.0000e-04 Epoch 47/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0047 - mae: 0.0288 - val_loss: 0.0037 - val_mae: 0.0265 - learning_rate: 1.0000e-04 Epoch 48/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0047 - mae: 0.0279 - val_loss: 0.0039 - val_mae: 0.0303 - learning_rate: 1.0000e-04 Epoch 49/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0045 - mae: 0.0271 - val_loss: 0.0044 - val_mae: 0.0373 - learning_rate: 1.0000e-04 Epoch 50/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0046 - mae: 0.0287 - val_loss: 0.0040 - val_mae: 0.0330 - learning_rate: 1.0000e-04 Epoch 51/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0048 - mae: 0.0294 - val_loss: 0.0032 - val_mae: 0.0207 - learning_rate: 1.0000e-04 Epoch 52/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0263 - val_loss: 0.0042 - val_mae: 0.0372 - learning_rate: 1.0000e-04 Epoch 53/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0045 - mae: 0.0293 - val_loss: 0.0038 - val_mae: 0.0317 - learning_rate: 1.0000e-04 Epoch 54/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0275 - val_loss: 0.0045 - val_mae: 0.0411 - learning_rate: 1.0000e-04 Epoch 55/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0285 - val_loss: 0.0041 - val_mae: 0.0371 - learning_rate: 1.0000e-04 Epoch 56/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0040 - mae: 0.0272 - val_loss: 0.0038 - val_mae: 0.0336 - learning_rate: 1.0000e-04 Epoch 57/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0276 - val_loss: 0.0031 - val_mae: 0.0252 - learning_rate: 1.0000e-04 Epoch 58/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0283 - val_loss: 0.0035 - val_mae: 0.0310 - learning_rate: 1.0000e-04 Epoch 59/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0265 - val_loss: 0.0026 - val_mae: 0.0175 - learning_rate: 1.0000e-04 Epoch 60/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0264 - val_loss: 0.0026 - val_mae: 0.0182 - learning_rate: 1.0000e-04 Restoring model weights from the end of the best epoch: 60.
Đánh giá mô hình 8:2¶
In [54]:
# Vẽ val_loss để đánh giá overfitting cho split 8:2
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 8:2 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất cho split 8:2
best_epoch_82 = np.argmin(history_rnn_82.history['val_loss']) + 1
best_val_loss_82 = min(history_rnn_82.history['val_loss'])
print(f"Epoch tốt nhất (8:2): {best_epoch_82} với val_loss: {best_val_loss_82:.6f}")
Epoch tốt nhất (8:2): 60 với val_loss: 0.002610
Dự đoán và trực quan hóa 8:2¶
In [55]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 8:2
last_data_scaled_82 = scaled_test_input_82[-time_step:]
forecasted_prices_30_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 30, scaler_target)
forecasted_prices_60_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 60, scaler_target)
forecasted_prices_90_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra cho split 8:2
test_predict_scaled_82 = model_rnn_82.predict(X_test_82)
test_predict_rnn_82 = scaler_target.inverse_transform(test_predict_scaled_82)
# Tạo DataFrame cho các dự đoán 8:2
forecast_dates_30_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30_82 = pd.DataFrame(forecasted_prices_30_82, index=forecast_dates_30_82, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecasted_prices_60_82, index=forecast_dates_60_82, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecasted_prices_90_82, index=forecast_dates_90_82, columns=['Price'])
# Trực quan hóa kết quả cho split 8:2
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test 8:2
plt.plot(test_data_82.index[time_step:], test_predict_rnn_82,
label='Dự đoán trên tập test (8:2)', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai 8:2
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
label='Dự đoán 30 ngày tiếp theo (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
label='Dự đoán 60 ngày tiếp theo (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
label='Dự đoán 90 ngày tiếp theo (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá ETH bằng RNN (8:2 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
20/20 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step
In [56]:
# Đánh giá mô hình 8:2
# Lấy giá trị thực tế trên tập test 8:2
y_test_actual_82 = test_data_82['Price'].values[time_step:]
# Tính toán các metrics cho split 8:2
mape_82 = mean_absolute_percentage_error(y_test_actual_82, test_predict_rnn_82.flatten())
mse_82 = mean_squared_error(y_test_actual_82, test_predict_rnn_82.flatten())
rmse_82 = np.sqrt(mse_82)
print(f'Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_82.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày cho 8:2
print(f'\nDự đoán giá ETH 30 ngày tiếp theo (8:2):')
print(f'Giá cao nhất: ${forecasted_prices_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_82.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_82.mean():.2f}')
Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = 50): MAPE: 0.03% MSE: 14668.50 RMSE: 121.11 Số epochs huấn luyện: 60 Dự đoán giá ETH 30 ngày tiếp theo (8:2): Giá cao nhất: $2543.50 Giá thấp nhất: $2458.92 Giá trung bình: $2484.52
Chia 9:1¶
Chuẩn hóa dữ liệu 9:1¶
In [57]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [58]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data) * 0.9)
train_data_91 = data.iloc[0:train_size_91,:]
test_data_91 = data.iloc[train_size_91:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input_91 = scaled_input[0:train_size_91,:]
scaled_test_input_91 = scaled_input[train_size_91:,:]
scaled_train_target_91 = scaled_target[0:train_size_91,:]
scaled_test_target_91 = scaled_target[train_size_91:,:]
print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3033 Kích thước tập test 9:1: 337
Xây dựng mô hình RNN¶
In [59]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [60]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [61]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình 9:1¶
In [62]:
# Tạo dữ liệu train và test với time_step = 50 cho split 9:1
X_train_91, y_train_91 = create_multivariate_time_series_data(scaled_train_input_91, scaled_train_target_91, time_step)
X_test_91, y_test_91 = create_multivariate_time_series_data(scaled_test_input_91, scaled_test_target_91, time_step)
print(f"X_train_91 shape: {X_train_91.shape}")
print(f"y_train_91 shape: {y_train_91.shape}")
print(f"X_test_91 shape: {X_test_91.shape}")
print(f"y_test_91 shape: {y_test_91.shape}")
# Xây dựng mô hình RNN cho split 9:1
model_rnn_91 = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop_91 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_91 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình 9:1
history_rnn_91 = model_rnn_91.fit(
X_train_91, y_train_91,
epochs=60,
batch_size=16,
validation_data=(X_test_91, y_test_91),
callbacks=[early_stop_91, reduce_lr_91],
verbose=1
)
X_train_91 shape: (2983, 50, 3) y_train_91 shape: (2983,) X_test_91 shape: (287, 50, 3) y_test_91 shape: (287,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
187/187 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - loss: 0.0360 - mae: 0.1110 - val_loss: 0.0089 - val_mae: 0.0467 - learning_rate: 1.0000e-04 Epoch 2/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0166 - mae: 0.0609 - val_loss: 0.0091 - val_mae: 0.0509 - learning_rate: 1.0000e-04 Epoch 3/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0142 - mae: 0.0539 - val_loss: 0.0112 - val_mae: 0.0663 - learning_rate: 1.0000e-04 Epoch 4/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0133 - mae: 0.0513 - val_loss: 0.0122 - val_mae: 0.0746 - learning_rate: 1.0000e-04 Epoch 5/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0126 - mae: 0.0490 - val_loss: 0.0120 - val_mae: 0.0735 - learning_rate: 1.0000e-04 Epoch 6/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0109 - mae: 0.0443 - val_loss: 0.0087 - val_mae: 0.0506 - learning_rate: 1.0000e-04 Epoch 7/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0112 - mae: 0.0460 - val_loss: 0.0151 - val_mae: 0.0928 - learning_rate: 1.0000e-04 Epoch 8/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0105 - mae: 0.0429 - val_loss: 0.0082 - val_mae: 0.0481 - learning_rate: 1.0000e-04 Epoch 9/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0101 - mae: 0.0415 - val_loss: 0.0126 - val_mae: 0.0800 - learning_rate: 1.0000e-04 Epoch 10/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0094 - mae: 0.0390 - val_loss: 0.0117 - val_mae: 0.0754 - learning_rate: 1.0000e-04 Epoch 11/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0098 - mae: 0.0417 - val_loss: 0.0076 - val_mae: 0.0442 - learning_rate: 1.0000e-04 Epoch 12/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0088 - mae: 0.0385 - val_loss: 0.0074 - val_mae: 0.0427 - learning_rate: 1.0000e-04 Epoch 13/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0092 - mae: 0.0390 - val_loss: 0.0087 - val_mae: 0.0554 - learning_rate: 1.0000e-04 Epoch 14/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0093 - mae: 0.0400 - val_loss: 0.0087 - val_mae: 0.0570 - learning_rate: 1.0000e-04 Epoch 15/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0086 - mae: 0.0393 - val_loss: 0.0076 - val_mae: 0.0481 - learning_rate: 1.0000e-04 Epoch 16/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0077 - mae: 0.0354 - val_loss: 0.0088 - val_mae: 0.0587 - learning_rate: 1.0000e-04 Epoch 17/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0076 - mae: 0.0349 - val_loss: 0.0069 - val_mae: 0.0434 - learning_rate: 1.0000e-04 Epoch 18/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0076 - mae: 0.0355 - val_loss: 0.0059 - val_mae: 0.0318 - learning_rate: 1.0000e-04 Epoch 19/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0077 - mae: 0.0356 - val_loss: 0.0076 - val_mae: 0.0511 - learning_rate: 1.0000e-04 Epoch 20/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0076 - mae: 0.0362 - val_loss: 0.0068 - val_mae: 0.0450 - learning_rate: 1.0000e-04 Epoch 21/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0075 - mae: 0.0357 - val_loss: 0.0066 - val_mae: 0.0429 - learning_rate: 1.0000e-04 Epoch 22/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0072 - mae: 0.0361 - val_loss: 0.0083 - val_mae: 0.0587 - learning_rate: 1.0000e-04 Epoch 23/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0070 - mae: 0.0344 - val_loss: 0.0090 - val_mae: 0.0652 - learning_rate: 1.0000e-04 Epoch 24/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0072 - mae: 0.0349 - val_loss: 0.0068 - val_mae: 0.0470 - learning_rate: 1.0000e-04 Epoch 25/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0068 - mae: 0.0345 - val_loss: 0.0061 - val_mae: 0.0418 - learning_rate: 1.0000e-04 Epoch 26/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0332 - val_loss: 0.0066 - val_mae: 0.0480 - learning_rate: 1.0000e-04 Epoch 27/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0063 - mae: 0.0328 - val_loss: 0.0083 - val_mae: 0.0630 - learning_rate: 1.0000e-04 Epoch 28/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0062 - mae: 0.0326 - val_loss: 0.0055 - val_mae: 0.0381 - learning_rate: 1.0000e-04 Epoch 29/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0065 - mae: 0.0334 - val_loss: 0.0067 - val_mae: 0.0505 - learning_rate: 1.0000e-04 Epoch 30/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0064 - mae: 0.0340 - val_loss: 0.0050 - val_mae: 0.0330 - learning_rate: 1.0000e-04 Epoch 31/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0059 - mae: 0.0316 - val_loss: 0.0053 - val_mae: 0.0380 - learning_rate: 1.0000e-04 Epoch 32/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0059 - mae: 0.0313 - val_loss: 0.0063 - val_mae: 0.0496 - learning_rate: 1.0000e-04 Epoch 33/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0059 - mae: 0.0333 - val_loss: 0.0071 - val_mae: 0.0557 - learning_rate: 1.0000e-04 Epoch 34/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0317 - val_loss: 0.0047 - val_mae: 0.0331 - learning_rate: 1.0000e-04 Epoch 35/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0055 - mae: 0.0317 - val_loss: 0.0057 - val_mae: 0.0447 - learning_rate: 1.0000e-04 Epoch 36/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0308 - val_loss: 0.0045 - val_mae: 0.0330 - learning_rate: 1.0000e-04 Epoch 37/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0324 - val_loss: 0.0055 - val_mae: 0.0434 - learning_rate: 1.0000e-04 Epoch 38/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0052 - mae: 0.0309 - val_loss: 0.0052 - val_mae: 0.0424 - learning_rate: 1.0000e-04 Epoch 39/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0053 - mae: 0.0312 - val_loss: 0.0048 - val_mae: 0.0370 - learning_rate: 1.0000e-04 Epoch 40/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0050 - mae: 0.0303 - val_loss: 0.0048 - val_mae: 0.0381 - learning_rate: 1.0000e-04 Epoch 41/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0299 - val_loss: 0.0037 - val_mae: 0.0255 - learning_rate: 1.0000e-04 Epoch 42/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0294 - val_loss: 0.0046 - val_mae: 0.0373 - learning_rate: 1.0000e-04 Epoch 43/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0049 - mae: 0.0312 - val_loss: 0.0035 - val_mae: 0.0247 - learning_rate: 1.0000e-04 Epoch 44/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0305 - val_loss: 0.0036 - val_mae: 0.0263 - learning_rate: 1.0000e-04 Epoch 45/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0046 - mae: 0.0302 - val_loss: 0.0039 - val_mae: 0.0314 - learning_rate: 1.0000e-04 Epoch 46/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0281 - val_loss: 0.0039 - val_mae: 0.0314 - learning_rate: 1.0000e-04 Epoch 47/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0295 - val_loss: 0.0039 - val_mae: 0.0328 - learning_rate: 1.0000e-04 Epoch 48/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0042 - mae: 0.0288 - val_loss: 0.0036 - val_mae: 0.0296 - learning_rate: 1.0000e-04 Epoch 49/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0292 - val_loss: 0.0042 - val_mae: 0.0377 - learning_rate: 1.0000e-04 Epoch 50/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0282 - val_loss: 0.0036 - val_mae: 0.0295 - learning_rate: 1.0000e-04 Epoch 51/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0304 - val_loss: 0.0036 - val_mae: 0.0306 - learning_rate: 1.0000e-04 Epoch 52/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0299 - val_loss: 0.0040 - val_mae: 0.0366 - learning_rate: 1.0000e-04 Epoch 53/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0307 - val_loss: 0.0027 - val_mae: 0.0199 - learning_rate: 1.0000e-04 Epoch 54/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0304 - val_loss: 0.0030 - val_mae: 0.0249 - learning_rate: 1.0000e-04 Epoch 55/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0281 - val_loss: 0.0034 - val_mae: 0.0312 - learning_rate: 1.0000e-04 Epoch 56/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0286 - val_loss: 0.0029 - val_mae: 0.0238 - learning_rate: 1.0000e-04 Epoch 57/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0285 - val_loss: 0.0032 - val_mae: 0.0296 - learning_rate: 1.0000e-04 Epoch 58/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0290 - val_loss: 0.0027 - val_mae: 0.0214 - learning_rate: 1.0000e-04 Epoch 59/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0288 - val_loss: 0.0030 - val_mae: 0.0274 - learning_rate: 1.0000e-04 Epoch 60/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0287 - val_loss: 0.0025 - val_mae: 0.0209 - learning_rate: 1.0000e-04 Restoring model weights from the end of the best epoch: 60.
Đánh giá mô hình 9:1¶
In [63]:
# Vẽ val_loss để đánh giá overfitting cho split 9:1
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 9:1 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất cho split 9:1
best_epoch_91 = np.argmin(history_rnn_91.history['val_loss']) + 1
best_val_loss_91 = min(history_rnn_91.history['val_loss'])
print(f"Epoch tốt nhất (9:1): {best_epoch_91} với val_loss: {best_val_loss_91:.6f}")
Epoch tốt nhất (9:1): 60 với val_loss: 0.002550
Dự đoán và trực quan hóa 9:1¶
In [64]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 9:1
last_data_scaled_91 = scaled_test_input_91[-time_step:]
forecasted_prices_30_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 30, scaler_target)
forecasted_prices_60_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 60, scaler_target)
forecasted_prices_90_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra cho split 9:1
test_predict_scaled_91 = model_rnn_91.predict(X_test_91)
test_predict_rnn_91 = scaler_target.inverse_transform(test_predict_scaled_91)
# Tạo DataFrame cho các dự đoán 9:1
forecast_dates_30_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30_91 = pd.DataFrame(forecasted_prices_30_91, index=forecast_dates_30_91, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecasted_prices_60_91, index=forecast_dates_60_91, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecasted_prices_90_91, index=forecast_dates_90_91, columns=['Price'])
# Trực quan hóa kết quả cho split 9:1
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test 9:1
plt.plot(test_data_91.index[time_step:], test_predict_rnn_91,
label='Dự đoán trên tập test (9:1)', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai 9:1
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
label='Dự đoán 30 ngày tiếp theo (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
label='Dự đoán 60 ngày tiếp theo (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
label='Dự đoán 90 ngày tiếp theo (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá ETH bằng RNN (9:1 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
In [65]:
# Đánh giá mô hình 9:1
# Lấy giá trị thực tế trên tập test 9:1
y_test_actual_91 = test_data_91['Price'].values[time_step:]
# Tính toán các metrics cho split 9:1
mape_91 = mean_absolute_percentage_error(y_test_actual_91, test_predict_rnn_91.flatten())
mse_91 = mean_squared_error(y_test_actual_91, test_predict_rnn_91.flatten())
rmse_91 = np.sqrt(mse_91)
print(f'Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_91.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày cho 9:1
print(f'\nDự đoán giá ETH 30 ngày tiếp theo (9:1):')
print(f'Giá cao nhất: ${forecasted_prices_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_91.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_91.mean():.2f}')
Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = 50): MAPE: 0.04% MSE: 18096.69 RMSE: 134.52 Số epochs huấn luyện: 60 Dự đoán giá ETH 30 ngày tiếp theo (9:1): Giá cao nhất: $2512.60 Giá thấp nhất: $2424.56 Giá trung bình: $2476.96
So sánh 3 tỉ lệ¶
In [66]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU")
print("="*80)
# Thu thập thông tin từ 3 splits
splits_info = {
'7:3': {
'train_size': len(train_data),
'test_size': len(test_data),
'mape': mape,
'mse': mse,
'rmse': rmse,
'epochs': len(history_rnn.history['loss']),
'best_val_loss': best_val_loss,
'best_epoch': best_epoch,
'final_train_loss': history_rnn.history['loss'][-1],
'final_val_loss': history_rnn.history['val_loss'][-1]
},
'8:2': {
'train_size': len(train_data_82),
'test_size': len(test_data_82),
'mape': mape_82,
'mse': mse_82,
'rmse': rmse_82,
'epochs': len(history_rnn_82.history['loss']),
'best_val_loss': best_val_loss_82,
'best_epoch': best_epoch_82,
'final_train_loss': history_rnn_82.history['loss'][-1],
'final_val_loss': history_rnn_82.history['val_loss'][-1]
},
'9:1': {
'train_size': len(train_data_91),
'test_size': len(test_data_91),
'mape': mape_91,
'mse': mse_91,
'rmse': rmse_91,
'epochs': len(history_rnn_91.history['loss']),
'best_val_loss': best_val_loss_91,
'best_epoch': best_epoch_91,
'final_train_loss': history_rnn_91.history['loss'][-1],
'final_val_loss': history_rnn_91.history['val_loss'][-1]
}
}
# In bảng so sánh
for split, info in splits_info.items():
print(f"\n{split} Split:")
print(f" Kích thước train: {info['train_size']:,} mẫu")
print(f" Kích thước test: {info['test_size']:,} mẫu")
print(f" MAPE: {info['mape']:.2f}%")
print(f" MSE: {info['mse']:,.2f}")
print(f" RMSE: {info['rmse']:,.2f}")
print(f" Số epochs: {info['epochs']}")
print(f" Best val_loss: {info['best_val_loss']:.6f} (epoch {info['best_epoch']})")
print(f" Final train_loss: {info['final_train_loss']:.6f}")
print(f" Final val_loss: {info['final_val_loss']:.6f}")
print(f" Overfitting gap: {abs(info['final_val_loss'] - info['final_train_loss']):.6f}")
================================================================================ SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU ================================================================================ 7:3 Split: Kích thước train: 2,359 mẫu Kích thước test: 1,011 mẫu MAPE: 0.03% MSE: 15,641.58 RMSE: 125.07 Số epochs: 60 Best val_loss: 0.003579 (epoch 58) Final train_loss: 0.005304 Final val_loss: 0.003708 Overfitting gap: 0.001596 8:2 Split: Kích thước train: 2,696 mẫu Kích thước test: 674 mẫu MAPE: 0.03% MSE: 14,668.50 RMSE: 121.11 Số epochs: 60 Best val_loss: 0.002610 (epoch 60) Final train_loss: 0.003832 Final val_loss: 0.002610 Overfitting gap: 0.001222 9:1 Split: Kích thước train: 3,033 mẫu Kích thước test: 337 mẫu MAPE: 0.04% MSE: 18,096.69 RMSE: 134.52 Số epochs: 60 Best val_loss: 0.002550 (epoch 60) Final train_loss: 0.003736 Final val_loss: 0.002550 Overfitting gap: 0.001186
In [67]:
# Vẽ biểu đồ so sánh các metrics
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
# 1. So sánh MAPE
mape_values = [splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 2. So sánh RMSE
rmse_values = [splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')
# 3. So sánh Best Validation Loss
best_val_loss_values = [splits_info[split]['best_val_loss'] for split in splits]
axes[0, 2].bar(splits, best_val_loss_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh Best Validation Loss', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('Best Val Loss')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(best_val_loss_values):
axes[0, 2].text(i, v + 0.0001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')
# 4. So sánh số epochs
epochs_values = [splits_info[split]['epochs'] for split in splits]
axes[1, 0].bar(splits, epochs_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh Số Epochs', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('Số Epochs')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(epochs_values):
axes[1, 0].text(i, v + 0.5, f'{v}', ha='center', va='bottom', fontweight='bold')
# 5. So sánh Overfitting Gap
overfitting_gap = [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits]
axes[1, 1].bar(splits, overfitting_gap, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Overfitting Gap', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('|Val Loss - Train Loss|')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(overfitting_gap):
axes[1, 1].text(i, v + 0.001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')
# 6. So sánh kích thước test set
test_sizes = [splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')
plt.tight_layout()
plt.show()
C:\Users\Hii\AppData\Local\Temp\ipykernel_28116\2283599594.py:62: UserWarning: Tight layout not applied. tight_layout cannot make Axes height small enough to accommodate all Axes decorations. plt.tight_layout()
In [68]:
# Vẽ so sánh Training và Validation Loss curves
plt.figure(figsize=(18, 6))
# Subplot 1: 7:3 Split
plt.subplot(1, 3, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('7:3 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch}')
# Subplot 2: 8:2 Split
plt.subplot(1, 3, 2)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('8:2 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_82-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_82}')
# Subplot 3: 9:1 Split
plt.subplot(1, 3, 3)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('9:1 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_91-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_91}')
plt.tight_layout()
plt.show()
In [69]:
# Tạo DataFrame tổng hợp kết quả để dễ so sánh
import pandas as pd
comparison_df = pd.DataFrame({
'Split': ['7:3', '8:2', '9:1'],
'Train_Size': [splits_info[split]['train_size'] for split in splits],
'Test_Size': [splits_info[split]['test_size'] for split in splits],
'MAPE (%)': [splits_info[split]['mape'] for split in splits],
'RMSE (USD)': [splits_info[split]['rmse'] for split in splits],
'MSE': [splits_info[split]['mse'] for split in splits],
'Best_Val_Loss': [splits_info[split]['best_val_loss'] for split in splits],
'Best_Epoch': [splits_info[split]['best_epoch'] for split in splits],
'Total_Epochs': [splits_info[split]['epochs'] for split in splits],
'Overfitting_Gap': [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits]
})
print("\nBẢNG TỔNG HỢP KẾT QUẢ:")
print("="*100)
print(comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ: ==================================================================================================== Split Train_Size Test_Size MAPE (%) RMSE (USD) MSE Best_Val_Loss Best_Epoch Total_Epochs Overfitting_Gap 7:3 2359 1011 0.0349 125.0663 15641.5773 0.0036 58 60 0.0016 8:2 2696 674 0.0314 121.1136 14668.5044 0.0026 60 60 0.0012 9:1 3033 337 0.0366 134.5239 18096.6919 0.0025 60 60 0.0012
In [70]:
# Phân tích và đưa ra khuyến nghị
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ")
print("="*80)
# Tìm split tốt nhất cho từng metric
best_mape_split = splits[np.argmin([splits_info[split]['mape'] for split in splits])]
best_rmse_split = splits[np.argmin([splits_info[split]['rmse'] for split in splits])]
best_val_loss_split = splits[np.argmin([splits_info[split]['best_val_loss'] for split in splits])]
best_overfitting_split = splits[np.argmin([abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits])]
print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:")
print(f" • Tốt nhất theo MAPE: {best_mape_split} ({splits_info[best_mape_split]['mape']:.2f}%)")
print(f" • Tốt nhất theo RMSE: {best_rmse_split} ({splits_info[best_rmse_split]['rmse']:,.2f} USD)")
print(f" • Tốt nhất theo Val Loss: {best_val_loss_split} ({splits_info[best_val_loss_split]['best_val_loss']:.6f})")
print(f" • Ít overfitting nhất: {best_overfitting_split} (gap: {abs(splits_info[best_overfitting_split]['final_val_loss'] - splits_info[best_overfitting_split]['final_train_loss']):.6f})")
# Tính điểm tổng hợp (rank-based scoring)
def calculate_rank_score(splits_info):
scores = {}
splits_list = list(splits_info.keys())
# Rank cho MAPE (thấp hơn = tốt hơn)
mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
# Rank cho RMSE (thấp hơn = tốt hơn)
rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
# Rank cho Best Val Loss (thấp hơn = tốt hơn)
val_loss_rank = sorted(splits_list, key=lambda x: splits_info[x]['best_val_loss'])
# Rank cho Overfitting Gap (thấp hơn = tốt hơn)
overfitting_rank = sorted(splits_list, key=lambda x: abs(splits_info[x]['final_val_loss'] - splits_info[x]['final_train_loss']))
for split in splits_list:
# Điểm rank (1 = tốt nhất, 3 = kém nhất)
score = (mape_rank.index(split) + 1) * 0.3 + \
(rmse_rank.index(split) + 1) * 0.3 + \
(val_loss_rank.index(split) + 1) * 0.25 + \
(overfitting_rank.index(split) + 1) * 0.15
scores[split] = score
return scores
# Tính điểm tổng hợp
scores = calculate_rank_score(splits_info)
best_overall_split = min(scores, key=scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):")
for split in splits_info.keys():
print(f" • {split}: {scores[split]:.2f} điểm")
print(f" • {split}: {scores[split]:.2f} điểm")
print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ:")
print(f" 🏆 MÔ HÌNH TỐT NHẤT: Split {best_overall_split}")
print(f" 📊 Lý do:")
print(f" - MAPE: {splits_info[best_overall_split]['mape']:.2f}%")
print(f" - RMSE: {splits_info[best_overall_split]['rmse']:,.2f} USD")
print(f" - Best Val Loss: {splits_info[best_overall_split]['best_val_loss']:.6f}")
print(f" - Overfitting Gap: {abs(splits_info[best_overall_split]['final_val_loss'] - splits_info[best_overall_split]['final_train_loss']):.6f}")
print(f" - Tập test có {splits_info[best_overall_split]['test_size']:,} mẫu (đủ để đánh giá)")
print(f" - Huấn luyện ổn định với {splits_info[best_overall_split]['epochs']} epochs")
print(f"\n4. NHẬN XÉT CHUNG:")
if best_overall_split == '7:3':
print(" • Split 7:3 cân bằng tốt giữa kích thước tập train và test")
print(" • Tập test đủ lớn để đánh giá độ tin cậy của mô hình")
print(" • Hiệu suất dự đoán tốt với mức overfitting chấp nhận được")
elif best_overall_split == '8:2':
print(" • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn")
print(" • Tập test vẫn đủ lớn để đánh giá")
print(" • Cân bằng tốt giữa hiệu suất và độ tin cậy")
else: # 9:1
print(" • Split 9:1 tối đa hóa dữ liệu train")
print(" • Có thể có rủi ro về độ tin cậy do tập test nhỏ")
print(" • Phù hợp khi có ít dữ liệu và cần tối ưu hiệu suất")
print(f"\n ⚠️ LƯU Ý: Với dữ liệu time series như ETH, nên chọn split cân bằng")
print(f" để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ
================================================================================
1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:
• Tốt nhất theo MAPE: 8:2 (0.03%)
• Tốt nhất theo RMSE: 8:2 (121.11 USD)
• Tốt nhất theo Val Loss: 9:1 (0.002550)
• Ít overfitting nhất: 9:1 (gap: 0.001186)
2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):
• 7:3: 2.40 điểm
• 7:3: 2.40 điểm
• 8:2: 1.40 điểm
• 8:2: 1.40 điểm
• 9:1: 2.20 điểm
• 9:1: 2.20 điểm
3. KẾT LUẬN VÀ KHUYẾN NGHỊ:
🏆 MÔ HÌNH TỐT NHẤT: Split 8:2
📊 Lý do:
- MAPE: 0.03%
- RMSE: 121.11 USD
- Best Val Loss: 0.002610
- Overfitting Gap: 0.001222
- Tập test có 674 mẫu (đủ để đánh giá)
- Huấn luyện ổn định với 60 epochs
4. NHẬN XÉT CHUNG:
• Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn
• Tập test vẫn đủ lớn để đánh giá
• Cân bằng tốt giữa hiệu suất và độ tin cậy
⚠️ LƯU Ý: Với dữ liệu time series như ETH, nên chọn split cân bằng
để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.
In [71]:
# Vẽ biểu đồ radar cho so sánh tổng thể
import numpy as np
import matplotlib.pyplot as plt
def create_radar_chart():
# Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
metrics = ['MAPE', 'RMSE', 'Val_Loss', 'Overfitting', 'Test_Size']
# Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
data = {}
for split in splits:
mape_norm = 1 - (splits_info[split]['mape'] - min([splits_info[s]['mape'] for s in splits])) / \
(max([splits_info[s]['mape'] for s in splits]) - min([splits_info[s]['mape'] for s in splits]))
rmse_norm = 1 - (splits_info[split]['rmse'] - min([splits_info[s]['rmse'] for s in splits])) / \
(max([splits_info[s]['rmse'] for s in splits]) - min([splits_info[s]['rmse'] for s in splits]))
val_loss_norm = 1 - (splits_info[split]['best_val_loss'] - min([splits_info[s]['best_val_loss'] for s in splits])) / \
(max([splits_info[s]['best_val_loss'] for s in splits]) - min([splits_info[s]['best_val_loss'] for s in splits]))
overfitting_gaps = [abs(splits_info[s]['final_val_loss'] - splits_info[s]['final_train_loss']) for s in splits]
overfitting_norm = 1 - (abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) - min(overfitting_gaps)) / \
(max(overfitting_gaps) - min(overfitting_gaps))
test_size_norm = (splits_info[split]['test_size'] - min([splits_info[s]['test_size'] for s in splits])) / \
(max([splits_info[s]['test_size'] for s in splits]) - min([splits_info[s]['test_size'] for s in splits]))
data[split] = [mape_norm, rmse_norm, val_loss_norm, overfitting_norm, test_size_norm]
# Tạo radar chart
angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
angles += angles[:1] # Complete the circle
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
for i, (split, values) in enumerate(data.items()):
values += values[:1] # Complete the circle
ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
ax.fill(angles, values, alpha=0.25, color=colors[i])
ax.set_xticks(angles[:-1])
ax.set_xticklabels(metrics)
ax.set_ylim(0, 1)
ax.set_title('So sánh tổng thể các Split (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
ax.grid(True)
plt.tight_layout()
plt.show()
create_radar_chart()
Kết luận cuối cùng¶
Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình RNN với split tỉ lệ dữ liệu tốt nhất đã được xác định.
Các yếu tố được xem xét:
- MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán
- RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
- Validation Loss: Đo lường hiệu suất trên tập validation
- Overfitting Gap: Đo lường mức độ overfitting
- Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá
Khuyến nghị sử dụng: Mô hình với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá ETH.
XRP¶
Import thư viện¶
In [72]:
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from keras.models import Sequential
from keras.layers import SimpleRNN, Dense, Dropout
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ReduceLROnPlateau
from keras import regularizers
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
XRP Dataset¶
Import csv¶
In [73]:
# Đọc file XRP
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\XRP Historical Data.csv"
data = pd.read_csv(file_path)
# Loại bỏ dấu phẩy và chuyển đổi thành float cho các cột Price và Open
for col in ['Price', 'Open']:
data[col] = data[col].astype(str).str.replace(',', '', regex=False).astype(float)
# Hàm xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
val = str(val).replace(',', '').strip()
if 'K' in val:
return float(val.replace('K', '')) * 1_000
elif 'M' in val:
return float(val.replace('M', '')) * 1_000_000
elif 'B' in val:
return float(val.replace('B', '')) * 1_000_000_000
else:
try:
return float(val)
except ValueError:
return np.nan # Nếu chuỗi rỗng hoặc không hợp lệ
# Áp dụng xử lý cho cột 'Vol.'
data['Vol.'] = data['Vol.'].apply(convert_volume)
# Kiểm tra NaN trước xử lý
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Nội suy và điền 0 nếu còn thiếu
data['Vol.'] = data['Vol.'].interpolate(method='linear')
data['Vol.'] = data['Vol.'].fillna(0)
# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Chuyển cột Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)
# Thông tin dữ liệu
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 12
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3369, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']
First 5 rows:
Price Open Vol.
Date
2016-03-10 0.0082 0.0081 59130.0
2016-03-11 0.0092 0.0082 25820.0
2016-03-12 0.0081 0.0092 78230.0
2016-03-13 0.0082 0.0081 620.0
2016-03-14 0.0083 0.0082 19310.0
Tổng số dữ liệu: 3369 dòng
Chia 7:3¶
Chuẩn hóa dữ liệu¶
In [74]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [75]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data) * 0.7)
train_data = data.iloc[0:train_size,:]
test_data = data.iloc[train_size:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input = scaled_input[0:train_size,:]
scaled_test_input = scaled_input[train_size:,:]
scaled_train_target = scaled_target[0:train_size,:]
scaled_test_target = scaled_target[train_size:,:]
print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
Kích thước tập train: 2358 Kích thước tập test: 1011
Xây dựng mô hình RNN¶
In [76]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [77]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [78]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình¶
In [79]:
# Tạo dữ liệu train và test với time_step = 50
time_step = 50
X_train, y_train = create_multivariate_time_series_data(scaled_train_input, scaled_train_target, time_step)
X_test, y_test = create_multivariate_time_series_data(scaled_test_input, scaled_test_target, time_step)
print(f"X_train shape: {X_train.shape}")
print(f"y_train shape: {y_train.shape}")
print(f"X_test shape: {X_test.shape}")
print(f"y_test shape: {y_test.shape}")
# Xây dựng mô hình RNN
model_rnn = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình
history_rnn = model_rnn.fit(
X_train, y_train,
epochs=60,
batch_size=16,
validation_data=(X_test, y_test),
callbacks=[early_stop, reduce_lr],
verbose=1
)
X_train shape: (2308, 50, 3) y_train shape: (2308,) X_test shape: (961, 50, 3) y_test shape: (961,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
145/145 ━━━━━━━━━━━━━━━━━━━━ 2s 7ms/step - loss: 0.0341 - mae: 0.1134 - val_loss: 0.0251 - val_mae: 0.0731 - learning_rate: 1.0000e-04 Epoch 2/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0148 - mae: 0.0617 - val_loss: 0.0133 - val_mae: 0.0468 - learning_rate: 1.0000e-04 Epoch 3/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0123 - mae: 0.0531 - val_loss: 0.0092 - val_mae: 0.0324 - learning_rate: 1.0000e-04 Epoch 4/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0112 - mae: 0.0475 - val_loss: 0.0083 - val_mae: 0.0304 - learning_rate: 1.0000e-04 Epoch 5/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0105 - mae: 0.0434 - val_loss: 0.0083 - val_mae: 0.0336 - learning_rate: 1.0000e-04 Epoch 6/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0098 - mae: 0.0410 - val_loss: 0.0069 - val_mae: 0.0261 - learning_rate: 1.0000e-04 Epoch 7/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0091 - mae: 0.0384 - val_loss: 0.0077 - val_mae: 0.0317 - learning_rate: 1.0000e-04 Epoch 8/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0092 - mae: 0.0374 - val_loss: 0.0071 - val_mae: 0.0270 - learning_rate: 1.0000e-04 Epoch 9/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0081 - mae: 0.0350 - val_loss: 0.0069 - val_mae: 0.0264 - learning_rate: 1.0000e-04 Epoch 10/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0084 - mae: 0.0345 - val_loss: 0.0066 - val_mae: 0.0292 - learning_rate: 1.0000e-04 Epoch 11/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0077 - mae: 0.0325 - val_loss: 0.0064 - val_mae: 0.0259 - learning_rate: 1.0000e-04 Epoch 12/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0326 - val_loss: 0.0058 - val_mae: 0.0180 - learning_rate: 1.0000e-04 Epoch 13/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0310 - val_loss: 0.0058 - val_mae: 0.0190 - learning_rate: 1.0000e-04 Epoch 14/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0074 - mae: 0.0302 - val_loss: 0.0065 - val_mae: 0.0297 - learning_rate: 1.0000e-04 Epoch 15/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0067 - mae: 0.0284 - val_loss: 0.0061 - val_mae: 0.0245 - learning_rate: 1.0000e-04 Epoch 16/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0069 - mae: 0.0282 - val_loss: 0.0057 - val_mae: 0.0216 - learning_rate: 1.0000e-04 Epoch 17/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0065 - mae: 0.0277 - val_loss: 0.0055 - val_mae: 0.0180 - learning_rate: 1.0000e-04 Epoch 18/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0061 - mae: 0.0256 - val_loss: 0.0060 - val_mae: 0.0270 - learning_rate: 1.0000e-04 Epoch 19/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0065 - mae: 0.0277 - val_loss: 0.0050 - val_mae: 0.0167 - learning_rate: 1.0000e-04 Epoch 20/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0062 - mae: 0.0273 - val_loss: 0.0050 - val_mae: 0.0183 - learning_rate: 1.0000e-04 Epoch 21/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0058 - mae: 0.0253 - val_loss: 0.0047 - val_mae: 0.0139 - learning_rate: 1.0000e-04 Epoch 22/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0056 - mae: 0.0242 - val_loss: 0.0053 - val_mae: 0.0218 - learning_rate: 1.0000e-04 Epoch 23/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0055 - mae: 0.0247 - val_loss: 0.0050 - val_mae: 0.0212 - learning_rate: 1.0000e-04 Epoch 24/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0054 - mae: 0.0243 - val_loss: 0.0046 - val_mae: 0.0155 - learning_rate: 1.0000e-04 Epoch 25/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0055 - mae: 0.0244 - val_loss: 0.0047 - val_mae: 0.0190 - learning_rate: 1.0000e-04 Epoch 26/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 0.0053 - mae: 0.0238 - val_loss: 0.0045 - val_mae: 0.0183 - learning_rate: 1.0000e-04 Epoch 27/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0050 - mae: 0.0230 - val_loss: 0.0047 - val_mae: 0.0199 - learning_rate: 1.0000e-04 Epoch 28/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0234 - val_loss: 0.0045 - val_mae: 0.0194 - learning_rate: 1.0000e-04 Epoch 29/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0050 - mae: 0.0228 - val_loss: 0.0039 - val_mae: 0.0128 - learning_rate: 1.0000e-04 Epoch 30/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0049 - mae: 0.0242 - val_loss: 0.0048 - val_mae: 0.0222 - learning_rate: 1.0000e-04 Epoch 31/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0044 - mae: 0.0211 - val_loss: 0.0041 - val_mae: 0.0169 - learning_rate: 1.0000e-04 Epoch 32/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0044 - mae: 0.0213 - val_loss: 0.0041 - val_mae: 0.0174 - learning_rate: 1.0000e-04 Epoch 33/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0042 - mae: 0.0215 - val_loss: 0.0041 - val_mae: 0.0190 - learning_rate: 1.0000e-04 Epoch 34/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0040 - mae: 0.0215 - val_loss: 0.0045 - val_mae: 0.0226 - learning_rate: 1.0000e-04 Epoch 35/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0040 - mae: 0.0215 - val_loss: 0.0035 - val_mae: 0.0145 - learning_rate: 1.0000e-04 Epoch 36/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0200 - val_loss: 0.0040 - val_mae: 0.0192 - learning_rate: 1.0000e-04 Epoch 37/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0039 - mae: 0.0206 - val_loss: 0.0039 - val_mae: 0.0186 - learning_rate: 1.0000e-04 Epoch 38/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0035 - mae: 0.0192 - val_loss: 0.0040 - val_mae: 0.0190 - learning_rate: 1.0000e-04 Epoch 39/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0210 - val_loss: 0.0036 - val_mae: 0.0168 - learning_rate: 1.0000e-04 Epoch 40/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0037 - mae: 0.0211 - val_loss: 0.0033 - val_mae: 0.0146 - learning_rate: 1.0000e-04 Epoch 41/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0034 - mae: 0.0200 - val_loss: 0.0033 - val_mae: 0.0153 - learning_rate: 1.0000e-04 Epoch 42/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0035 - mae: 0.0206 - val_loss: 0.0036 - val_mae: 0.0184 - learning_rate: 1.0000e-04 Epoch 43/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0031 - mae: 0.0190 - val_loss: 0.0033 - val_mae: 0.0170 - learning_rate: 1.0000e-04 Epoch 44/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0200 - val_loss: 0.0032 - val_mae: 0.0163 - learning_rate: 1.0000e-04 Epoch 45/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0031 - mae: 0.0195 - val_loss: 0.0032 - val_mae: 0.0167 - learning_rate: 1.0000e-04 Epoch 46/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0029 - mae: 0.0192 - val_loss: 0.0035 - val_mae: 0.0196 - learning_rate: 1.0000e-04 Epoch 47/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0027 - mae: 0.0179 - val_loss: 0.0030 - val_mae: 0.0167 - learning_rate: 1.0000e-04 Epoch 48/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0028 - mae: 0.0192 - val_loss: 0.0027 - val_mae: 0.0160 - learning_rate: 1.0000e-04 Epoch 49/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0028 - mae: 0.0187 - val_loss: 0.0028 - val_mae: 0.0156 - learning_rate: 1.0000e-04 Epoch 50/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0170 - val_loss: 0.0025 - val_mae: 0.0143 - learning_rate: 1.0000e-04 Epoch 51/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0176 - val_loss: 0.0027 - val_mae: 0.0154 - learning_rate: 1.0000e-04 Epoch 52/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0027 - mae: 0.0188 - val_loss: 0.0027 - val_mae: 0.0163 - learning_rate: 1.0000e-04 Epoch 53/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0171 - val_loss: 0.0036 - val_mae: 0.0222 - learning_rate: 1.0000e-04 Epoch 54/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0175 - val_loss: 0.0026 - val_mae: 0.0174 - learning_rate: 1.0000e-04 Epoch 55/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0022 - mae: 0.0176 - val_loss: 0.0031 - val_mae: 0.0196 - learning_rate: 1.0000e-04 Epoch 56/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0183 - val_loss: 0.0033 - val_mae: 0.0233 - learning_rate: 1.0000e-04 Epoch 57/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0021 - mae: 0.0168 - val_loss: 0.0024 - val_mae: 0.0155 - learning_rate: 1.0000e-04 Epoch 58/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0021 - mae: 0.0171 - val_loss: 0.0028 - val_mae: 0.0186 - learning_rate: 1.0000e-04 Epoch 59/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0168 - val_loss: 0.0030 - val_mae: 0.0201 - learning_rate: 1.0000e-04 Epoch 60/60 145/145 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0167 - val_loss: 0.0026 - val_mae: 0.0179 - learning_rate: 1.0000e-04 Restoring model weights from the end of the best epoch: 57.
Đánh giá mô hình¶
In [80]:
# Vẽ val_loss để đánh giá overfitting
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất
best_epoch = np.argmin(history_rnn.history['val_loss']) + 1
best_val_loss = min(history_rnn.history['val_loss'])
print(f"Epoch tốt nhất: {best_epoch} với val_loss: {best_val_loss:.6f}")
Epoch tốt nhất: 57 với val_loss: 0.002361
Dự đoán và trực quan hóa¶
In [81]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_data_scaled = scaled_test_input[-time_step:]
forecasted_prices_30 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 30, scaler_target)
forecasted_prices_60 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 60, scaler_target)
forecasted_prices_90 = forecast_multivariate_prices(model_rnn, last_data_scaled, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra
test_predict_scaled = model_rnn.predict(X_test)
test_predict_rnn = scaler_target.inverse_transform(test_predict_scaled)
# Tạo DataFrame cho các dự đoán
forecast_dates_30 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30 = pd.DataFrame(forecasted_prices_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecasted_prices_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecasted_prices_90, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa kết quả
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test
plt.plot(test_data.index[time_step:], test_predict_rnn,
label='Dự đoán trên tập test', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
label='Dự đoán 30 ngày tiếp theo', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'],
label='Dự đoán 60 ngày tiếp theo', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'],
label='Dự đoán 90 ngày tiếp theo', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá XRP bằng RNN (Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
31/31 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step
In [82]:
# Đánh giá mô hình
# Lấy giá trị thực tế trên tập test
y_test_actual = test_data['Price'].values[time_step:]
# Tính toán các metrics
mape = mean_absolute_percentage_error(y_test_actual, test_predict_rnn.flatten())
mse = mean_squared_error(y_test_actual, test_predict_rnn.flatten())
rmse = np.sqrt(mse)
print(f'Kết quả đánh giá mô hình RNN (Time Step = {time_step}):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày
print(f'\nDự đoán giá XRP 30 ngày tiếp theo:')
print(f'Giá cao nhất: ${forecasted_prices_30.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30.mean():.2f}')
Kết quả đánh giá mô hình RNN (Time Step = 50): MAPE: 0.04% MSE: 0.01 RMSE: 0.10 Số epochs huấn luyện: 60 Dự đoán giá XRP 30 ngày tiếp theo: Giá cao nhất: $1.99 Giá thấp nhất: $1.38 Giá trung bình: $1.57
Chia 8:2¶
Chuẩn hóa dữ liệu 8:2¶
In [83]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [84]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data) * 0.8)
train_data_82 = data.iloc[0:train_size_82,:]
test_data_82 = data.iloc[train_size_82:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input_82 = scaled_input[0:train_size_82,:]
scaled_test_input_82 = scaled_input[train_size_82:,:]
scaled_train_target_82 = scaled_target[0:train_size_82,:]
scaled_test_target_82 = scaled_target[train_size_82:,:]
print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2695 Kích thước tập test 8:2: 674
Xây dựng mô hình RNN¶
In [85]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [86]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [87]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình 8:2¶
In [88]:
# Tạo dữ liệu train và test với time_step = 50 cho split 8:2
X_train_82, y_train_82 = create_multivariate_time_series_data(scaled_train_input_82, scaled_train_target_82, time_step)
X_test_82, y_test_82 = create_multivariate_time_series_data(scaled_test_input_82, scaled_test_target_82, time_step)
print(f"X_train_82 shape: {X_train_82.shape}")
print(f"y_train_82 shape: {y_train_82.shape}")
print(f"X_test_82 shape: {X_test_82.shape}")
print(f"y_test_82 shape: {y_test_82.shape}")
# Xây dựng mô hình RNN cho split 8:2
model_rnn_82 = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop_82 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_82 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình 8:2
history_rnn_82 = model_rnn_82.fit(
X_train_82, y_train_82,
epochs=60,
batch_size=16,
validation_data=(X_test_82, y_test_82),
callbacks=[early_stop_82, reduce_lr_82],
verbose=1
)
X_train_82 shape: (2645, 50, 3) y_train_82 shape: (2645,) X_test_82 shape: (624, 50, 3) y_test_82 shape: (624,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
166/166 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - loss: 0.0412 - mae: 0.1294 - val_loss: 0.0214 - val_mae: 0.0753 - learning_rate: 1.0000e-04 Epoch 2/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0170 - mae: 0.0689 - val_loss: 0.0112 - val_mae: 0.0455 - learning_rate: 1.0000e-04 Epoch 3/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0127 - mae: 0.0545 - val_loss: 0.0090 - val_mae: 0.0336 - learning_rate: 1.0000e-04 Epoch 4/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0117 - mae: 0.0485 - val_loss: 0.0073 - val_mae: 0.0237 - learning_rate: 1.0000e-04 Epoch 5/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0108 - mae: 0.0464 - val_loss: 0.0071 - val_mae: 0.0236 - learning_rate: 1.0000e-04 Epoch 6/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0096 - mae: 0.0402 - val_loss: 0.0087 - val_mae: 0.0442 - learning_rate: 1.0000e-04 Epoch 7/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0092 - mae: 0.0381 - val_loss: 0.0085 - val_mae: 0.0400 - learning_rate: 1.0000e-04 Epoch 8/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0089 - mae: 0.0359 - val_loss: 0.0075 - val_mae: 0.0311 - learning_rate: 1.0000e-04 Epoch 9/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0079 - mae: 0.0334 - val_loss: 0.0071 - val_mae: 0.0280 - learning_rate: 1.0000e-04 Epoch 10/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0325 - val_loss: 0.0073 - val_mae: 0.0332 - learning_rate: 1.0000e-04 Epoch 11/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0079 - mae: 0.0317 - val_loss: 0.0067 - val_mae: 0.0249 - learning_rate: 1.0000e-04 Epoch 12/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0075 - mae: 0.0316 - val_loss: 0.0065 - val_mae: 0.0249 - learning_rate: 1.0000e-04 Epoch 13/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0069 - mae: 0.0290 - val_loss: 0.0070 - val_mae: 0.0316 - learning_rate: 1.0000e-04 Epoch 14/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0071 - mae: 0.0293 - val_loss: 0.0065 - val_mae: 0.0278 - learning_rate: 1.0000e-04 Epoch 15/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0268 - val_loss: 0.0079 - val_mae: 0.0394 - learning_rate: 1.0000e-04 Epoch 16/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0064 - mae: 0.0270 - val_loss: 0.0064 - val_mae: 0.0279 - learning_rate: 1.0000e-04 Epoch 17/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0068 - mae: 0.0270 - val_loss: 0.0063 - val_mae: 0.0293 - learning_rate: 1.0000e-04 Epoch 18/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0059 - mae: 0.0244 - val_loss: 0.0059 - val_mae: 0.0234 - learning_rate: 1.0000e-04 Epoch 19/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0252 - val_loss: 0.0061 - val_mae: 0.0299 - learning_rate: 1.0000e-04 Epoch 20/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0265 - val_loss: 0.0064 - val_mae: 0.0319 - learning_rate: 1.0000e-04 Epoch 21/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0055 - mae: 0.0230 - val_loss: 0.0062 - val_mae: 0.0315 - learning_rate: 1.0000e-04 Epoch 22/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0235 - val_loss: 0.0052 - val_mae: 0.0222 - learning_rate: 1.0000e-04 Epoch 23/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0052 - mae: 0.0232 - val_loss: 0.0052 - val_mae: 0.0234 - learning_rate: 1.0000e-04 Epoch 24/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0054 - mae: 0.0231 - val_loss: 0.0055 - val_mae: 0.0308 - learning_rate: 1.0000e-04 Epoch 25/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0051 - mae: 0.0219 - val_loss: 0.0066 - val_mae: 0.0373 - learning_rate: 1.0000e-04 Epoch 26/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0223 - val_loss: 0.0051 - val_mae: 0.0219 - learning_rate: 1.0000e-04 Epoch 27/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0047 - mae: 0.0209 - val_loss: 0.0056 - val_mae: 0.0329 - learning_rate: 1.0000e-04 Epoch 28/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0203 - val_loss: 0.0055 - val_mae: 0.0304 - learning_rate: 1.0000e-04 Epoch 29/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0043 - mae: 0.0210 - val_loss: 0.0046 - val_mae: 0.0201 - learning_rate: 1.0000e-04 Epoch 30/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0042 - mae: 0.0202 - val_loss: 0.0046 - val_mae: 0.0211 - learning_rate: 1.0000e-04 Epoch 31/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0043 - mae: 0.0219 - val_loss: 0.0053 - val_mae: 0.0292 - learning_rate: 1.0000e-04 Epoch 32/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0040 - mae: 0.0200 - val_loss: 0.0069 - val_mae: 0.0417 - learning_rate: 1.0000e-04 Epoch 33/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0040 - mae: 0.0206 - val_loss: 0.0060 - val_mae: 0.0333 - learning_rate: 1.0000e-04 Epoch 34/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0196 - val_loss: 0.0040 - val_mae: 0.0187 - learning_rate: 1.0000e-04 Epoch 35/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0038 - mae: 0.0202 - val_loss: 0.0045 - val_mae: 0.0240 - learning_rate: 1.0000e-04 Epoch 36/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0038 - mae: 0.0193 - val_loss: 0.0051 - val_mae: 0.0313 - learning_rate: 1.0000e-04 Epoch 37/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0035 - mae: 0.0195 - val_loss: 0.0040 - val_mae: 0.0219 - learning_rate: 1.0000e-04 Epoch 38/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0033 - mae: 0.0183 - val_loss: 0.0062 - val_mae: 0.0370 - learning_rate: 1.0000e-04 Epoch 39/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0032 - mae: 0.0188 - val_loss: 0.0036 - val_mae: 0.0187 - learning_rate: 1.0000e-04 Epoch 40/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0033 - mae: 0.0183 - val_loss: 0.0047 - val_mae: 0.0313 - learning_rate: 1.0000e-04 Epoch 41/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0031 - mae: 0.0187 - val_loss: 0.0051 - val_mae: 0.0337 - learning_rate: 1.0000e-04 Epoch 42/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0030 - mae: 0.0180 - val_loss: 0.0044 - val_mae: 0.0285 - learning_rate: 1.0000e-04 Epoch 43/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0032 - mae: 0.0193 - val_loss: 0.0041 - val_mae: 0.0265 - learning_rate: 1.0000e-04 Epoch 44/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0029 - mae: 0.0183 - val_loss: 0.0038 - val_mae: 0.0236 - learning_rate: 1.0000e-04 Epoch 45/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0167 - val_loss: 0.0032 - val_mae: 0.0197 - learning_rate: 1.0000e-04 Epoch 46/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0027 - mae: 0.0172 - val_loss: 0.0043 - val_mae: 0.0274 - learning_rate: 1.0000e-04 Epoch 47/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0181 - val_loss: 0.0076 - val_mae: 0.0502 - learning_rate: 1.0000e-04 Epoch 48/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0183 - val_loss: 0.0048 - val_mae: 0.0319 - learning_rate: 1.0000e-04 Epoch 49/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0173 - val_loss: 0.0026 - val_mae: 0.0156 - learning_rate: 1.0000e-04 Epoch 50/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0175 - val_loss: 0.0051 - val_mae: 0.0341 - learning_rate: 1.0000e-04 Epoch 51/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0174 - val_loss: 0.0035 - val_mae: 0.0244 - learning_rate: 1.0000e-04 Epoch 52/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0024 - mae: 0.0174 - val_loss: 0.0038 - val_mae: 0.0288 - learning_rate: 1.0000e-04 Epoch 53/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0022 - mae: 0.0167 - val_loss: 0.0039 - val_mae: 0.0284 - learning_rate: 1.0000e-04 Epoch 54/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0176 - val_loss: 0.0030 - val_mae: 0.0220 - learning_rate: 1.0000e-04 Epoch 55/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0023 - mae: 0.0176 - val_loss: 0.0029 - val_mae: 0.0220 - learning_rate: 1.0000e-04 Epoch 56/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0021 - mae: 0.0165 - val_loss: 0.0029 - val_mae: 0.0219 - learning_rate: 1.0000e-04 Epoch 57/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0163 - val_loss: 0.0021 - val_mae: 0.0160 - learning_rate: 1.0000e-04 Epoch 58/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0169 - val_loss: 0.0039 - val_mae: 0.0291 - learning_rate: 1.0000e-04 Epoch 59/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0019 - mae: 0.0165 - val_loss: 0.0034 - val_mae: 0.0264 - learning_rate: 1.0000e-04 Epoch 60/60 166/166 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0019 - mae: 0.0162 - val_loss: 0.0023 - val_mae: 0.0180 - learning_rate: 1.0000e-04 Restoring model weights from the end of the best epoch: 57.
Đánh giá mô hình 8:2¶
In [89]:
# Vẽ val_loss để đánh giá overfitting cho split 8:2
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 8:2 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất cho split 8:2
best_epoch_82 = np.argmin(history_rnn_82.history['val_loss']) + 1
best_val_loss_82 = min(history_rnn_82.history['val_loss'])
print(f"Epoch tốt nhất (8:2): {best_epoch_82} với val_loss: {best_val_loss_82:.6f}")
Epoch tốt nhất (8:2): 57 với val_loss: 0.002149
Dự đoán và trực quan hóa 8:2¶
In [90]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 8:2
last_data_scaled_82 = scaled_test_input_82[-time_step:]
forecasted_prices_30_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 30, scaler_target)
forecasted_prices_60_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 60, scaler_target)
forecasted_prices_90_82 = forecast_multivariate_prices(model_rnn_82, last_data_scaled_82, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra cho split 8:2
test_predict_scaled_82 = model_rnn_82.predict(X_test_82)
test_predict_rnn_82 = scaler_target.inverse_transform(test_predict_scaled_82)
# Tạo DataFrame cho các dự đoán 8:2
forecast_dates_30_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_82 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30_82 = pd.DataFrame(forecasted_prices_30_82, index=forecast_dates_30_82, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecasted_prices_60_82, index=forecast_dates_60_82, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecasted_prices_90_82, index=forecast_dates_90_82, columns=['Price'])
# Trực quan hóa kết quả cho split 8:2
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test 8:2
plt.plot(test_data_82.index[time_step:], test_predict_rnn_82,
label='Dự đoán trên tập test (8:2)', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai 8:2
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
label='Dự đoán 30 ngày tiếp theo (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
label='Dự đoán 60 ngày tiếp theo (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
label='Dự đoán 90 ngày tiếp theo (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá XRP bằng RNN (8:2 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
20/20 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step
In [91]:
# Đánh giá mô hình 8:2
# Lấy giá trị thực tế trên tập test 8:2
y_test_actual_82 = test_data_82['Price'].values[time_step:]
# Tính toán các metrics cho split 8:2
mape_82 = mean_absolute_percentage_error(y_test_actual_82, test_predict_rnn_82.flatten())
mse_82 = mean_squared_error(y_test_actual_82, test_predict_rnn_82.flatten())
rmse_82 = np.sqrt(mse_82)
print(f'Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_82.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày cho 8:2
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (8:2):')
print(f'Giá cao nhất: ${forecasted_prices_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_82.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_82.mean():.2f}')
Kết quả đánh giá mô hình RNN 8:2 Split (Time Step = 50): MAPE: 0.04% MSE: 0.01 RMSE: 0.10 Số epochs huấn luyện: 60 Dự đoán giá XRP 30 ngày tiếp theo (8:2): Giá cao nhất: $2.07 Giá thấp nhất: $1.98 Giá trung bình: $2.02
Chia 9:1¶
Chuẩn hóa dữ liệu 9:1¶
In [92]:
# Chuẩn hóa dữ liệu
# Lấy 3 cột Price, Open, Vol. để làm đầu vào và Price làm đầu ra
input_features = data[['Price', 'Open', 'Vol.']].values
target_feature = data[['Price']].values
# Áp dụng MinMaxScaler cho input features
scaler_input = MinMaxScaler(feature_range=(0, 1))
scaled_input = scaler_input.fit_transform(input_features)
# Áp dụng MinMaxScaler cho target feature
scaler_target = MinMaxScaler(feature_range=(0, 1))
scaled_target = scaler_target.fit_transform(target_feature)
In [93]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data) * 0.9)
train_data_91 = data.iloc[0:train_size_91,:]
test_data_91 = data.iloc[train_size_91:len(data),:]
# Chia dữ liệu đã chuẩn hóa
scaled_train_input_91 = scaled_input[0:train_size_91,:]
scaled_test_input_91 = scaled_input[train_size_91:,:]
scaled_train_target_91 = scaled_target[0:train_size_91,:]
scaled_test_target_91 = scaled_target[train_size_91:,:]
print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3032 Kích thước tập test 9:1: 337
Xây dựng mô hình RNN¶
In [94]:
def build_rnn_model_with_regularization(time_step: int, num_features: int) -> Sequential:
"""
Xây dựng mô hình RNN với regularization
Args:
time_step: Số time steps để nhìn về quá khứ
num_features: Số features đầu vào (Price, Open, Vol = 3)
Returns:
Sequential model
"""
model = Sequential()
# SimpleRNN layer với regularization
model.add(SimpleRNN(
units=50, # Số neurons
input_shape=(time_step, num_features), # (50, 3)
kernel_regularizer=regularizers.l2(0.001), # L2 regularization
return_sequences=False # Chỉ cần output cuối cùng
))
# Dropout để tránh overfitting
model.add(Dropout(0.3))
# Dense layer ẩn
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.2))
# Output layer: dự đoán 1 giá trị (Price)
model.add(Dense(1))
# Optimizer với learning rate nhỏ
optimizer = Adam(learning_rate=1e-4)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mae'])
return model
In [95]:
# Hàm tạo dữ liệu time series với multiple features
def create_multivariate_time_series_data(input_data: np.ndarray, target_data: np.ndarray, time_step: int) -> Tuple[np.ndarray, np.ndarray]:
X_data, y_data = [], []
for i in range(len(input_data) - time_step):
X_data.append(input_data[i:(i + time_step), :]) # Lấy tất cả features
y_data.append(target_data[i + time_step, 0]) # Chỉ lấy Price
return np.array(X_data), np.array(y_data)
In [96]:
# Hàm dự đoán với multiple features
def forecast_multivariate_prices(model: Sequential, input_data: np.ndarray, time_step: int,
forecast_days: int, scaler_target: MinMaxScaler) -> np.ndarray:
temp_input = input_data[-time_step:].reshape(1, time_step, input_data.shape[1])
lst_output = []
for _ in range(forecast_days):
predicted_price = model.predict(temp_input, verbose=0)
lst_output.append(predicted_price[0].tolist())
# Tạo input mới cho prediction tiếp theo
# Giả sử các features khác không đổi, chỉ cập nhật Price
new_row = temp_input[0, -1, :].copy()
new_row[0] = predicted_price[0, 0] # Cập nhật Price prediction
temp_input = np.append(temp_input[:, 1:, :],
new_row.reshape(1, 1, input_data.shape[1]), axis=1)
# Chuyển đổi lst_output thành numpy array và inverse transform
lst_output = np.array(lst_output).reshape(-1, 1)
return scaler_target.inverse_transform(lst_output)
Huấn luyện mô hình 9:1¶
In [97]:
# Tạo dữ liệu train và test với time_step = 50 cho split 9:1
X_train_91, y_train_91 = create_multivariate_time_series_data(scaled_train_input_91, scaled_train_target_91, time_step)
X_test_91, y_test_91 = create_multivariate_time_series_data(scaled_test_input_91, scaled_test_target_91, time_step)
print(f"X_train_91 shape: {X_train_91.shape}")
print(f"y_train_91 shape: {y_train_91.shape}")
print(f"X_test_91 shape: {X_test_91.shape}")
print(f"y_test_91 shape: {y_test_91.shape}")
# Xây dựng mô hình RNN cho split 9:1
model_rnn_91 = build_rnn_model_with_regularization(time_step, 3) # 3 features: Price, Open, Vol
# Callbacks để tối ưu hóa
early_stop_91 = EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True, verbose=1)
reduce_lr_91 = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10, min_lr=1e-7, verbose=1)
# Huấn luyện mô hình 9:1
history_rnn_91 = model_rnn_91.fit(
X_train_91, y_train_91,
epochs=60,
batch_size=16,
validation_data=(X_test_91, y_test_91),
callbacks=[early_stop_91, reduce_lr_91],
verbose=1
)
X_train_91 shape: (2982, 50, 3) y_train_91 shape: (2982,) X_test_91 shape: (287, 50, 3) y_test_91 shape: (287,) Epoch 1/60
c:\Users\Hii\AppData\Local\Programs\Python\Python311\Lib\site-packages\keras\src\layers\rnn\rnn.py:199: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead. super().__init__(**kwargs)
187/187 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - loss: 0.0511 - mae: 0.1464 - val_loss: 0.0229 - val_mae: 0.1005 - learning_rate: 1.0000e-04 Epoch 2/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0158 - mae: 0.0662 - val_loss: 0.0190 - val_mae: 0.0880 - learning_rate: 1.0000e-04 Epoch 3/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0132 - mae: 0.0561 - val_loss: 0.0174 - val_mae: 0.0879 - learning_rate: 1.0000e-04 Epoch 4/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0117 - mae: 0.0494 - val_loss: 0.0113 - val_mae: 0.0561 - learning_rate: 1.0000e-04 Epoch 5/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0106 - mae: 0.0447 - val_loss: 0.0151 - val_mae: 0.0784 - learning_rate: 1.0000e-04 Epoch 6/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0091 - mae: 0.0391 - val_loss: 0.0214 - val_mae: 0.1064 - learning_rate: 1.0000e-04 Epoch 7/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0087 - mae: 0.0372 - val_loss: 0.0150 - val_mae: 0.0785 - learning_rate: 1.0000e-04 Epoch 8/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0083 - mae: 0.0356 - val_loss: 0.0101 - val_mae: 0.0513 - learning_rate: 1.0000e-04 Epoch 9/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0080 - mae: 0.0333 - val_loss: 0.0110 - val_mae: 0.0575 - learning_rate: 1.0000e-04 Epoch 10/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0082 - mae: 0.0340 - val_loss: 0.0101 - val_mae: 0.0545 - learning_rate: 1.0000e-04 Epoch 11/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0074 - mae: 0.0315 - val_loss: 0.0132 - val_mae: 0.0731 - learning_rate: 1.0000e-04 Epoch 12/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0072 - mae: 0.0287 - val_loss: 0.0120 - val_mae: 0.0674 - learning_rate: 1.0000e-04 Epoch 13/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0071 - mae: 0.0300 - val_loss: 0.0128 - val_mae: 0.0695 - learning_rate: 1.0000e-04 Epoch 14/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0277 - val_loss: 0.0133 - val_mae: 0.0729 - learning_rate: 1.0000e-04 Epoch 15/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0066 - mae: 0.0266 - val_loss: 0.0118 - val_mae: 0.0656 - learning_rate: 1.0000e-04 Epoch 16/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0248 - val_loss: 0.0127 - val_mae: 0.0734 - learning_rate: 1.0000e-04 Epoch 17/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0247 - val_loss: 0.0110 - val_mae: 0.0646 - learning_rate: 1.0000e-04 Epoch 18/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0060 - mae: 0.0251 - val_loss: 0.0084 - val_mae: 0.0478 - learning_rate: 1.0000e-04 Epoch 19/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0059 - mae: 0.0243 - val_loss: 0.0117 - val_mae: 0.0703 - learning_rate: 1.0000e-04 Epoch 20/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0055 - mae: 0.0236 - val_loss: 0.0113 - val_mae: 0.0669 - learning_rate: 1.0000e-04 Epoch 21/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0056 - mae: 0.0252 - val_loss: 0.0082 - val_mae: 0.0493 - learning_rate: 1.0000e-04 Epoch 22/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0053 - mae: 0.0237 - val_loss: 0.0070 - val_mae: 0.0415 - learning_rate: 1.0000e-04 Epoch 23/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0050 - mae: 0.0219 - val_loss: 0.0116 - val_mae: 0.0710 - learning_rate: 1.0000e-04 Epoch 24/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0048 - mae: 0.0223 - val_loss: 0.0081 - val_mae: 0.0514 - learning_rate: 1.0000e-04 Epoch 25/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0048 - mae: 0.0207 - val_loss: 0.0081 - val_mae: 0.0517 - learning_rate: 1.0000e-04 Epoch 26/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0047 - mae: 0.0209 - val_loss: 0.0086 - val_mae: 0.0539 - learning_rate: 1.0000e-04 Epoch 27/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0044 - mae: 0.0213 - val_loss: 0.0090 - val_mae: 0.0599 - learning_rate: 1.0000e-04 Epoch 28/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0193 - val_loss: 0.0090 - val_mae: 0.0588 - learning_rate: 1.0000e-04 Epoch 29/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0041 - mae: 0.0198 - val_loss: 0.0123 - val_mae: 0.0771 - learning_rate: 1.0000e-04 Epoch 30/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0039 - mae: 0.0193 - val_loss: 0.0061 - val_mae: 0.0388 - learning_rate: 1.0000e-04 Epoch 31/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0183 - val_loss: 0.0079 - val_mae: 0.0552 - learning_rate: 1.0000e-04 Epoch 32/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0036 - mae: 0.0184 - val_loss: 0.0073 - val_mae: 0.0510 - learning_rate: 1.0000e-04 Epoch 33/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0037 - mae: 0.0181 - val_loss: 0.0091 - val_mae: 0.0623 - learning_rate: 1.0000e-04 Epoch 34/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0034 - mae: 0.0184 - val_loss: 0.0107 - val_mae: 0.0704 - learning_rate: 1.0000e-04 Epoch 35/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0033 - mae: 0.0176 - val_loss: 0.0117 - val_mae: 0.0774 - learning_rate: 1.0000e-04 Epoch 36/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0034 - mae: 0.0183 - val_loss: 0.0077 - val_mae: 0.0554 - learning_rate: 1.0000e-04 Epoch 37/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0032 - mae: 0.0178 - val_loss: 0.0089 - val_mae: 0.0653 - learning_rate: 1.0000e-04 Epoch 38/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0029 - mae: 0.0174 - val_loss: 0.0086 - val_mae: 0.0624 - learning_rate: 1.0000e-04 Epoch 39/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0031 - mae: 0.0182 - val_loss: 0.0064 - val_mae: 0.0484 - learning_rate: 1.0000e-04 Epoch 40/60 180/187 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0027 - mae: 0.0162 Epoch 40: ReduceLROnPlateau reducing learning rate to 4.999999873689376e-05. 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0027 - mae: 0.0162 - val_loss: 0.0063 - val_mae: 0.0474 - learning_rate: 1.0000e-04 Epoch 41/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 0.0028 - mae: 0.0174 - val_loss: 0.0049 - val_mae: 0.0374 - learning_rate: 5.0000e-05 Epoch 42/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0026 - mae: 0.0166 - val_loss: 0.0073 - val_mae: 0.0552 - learning_rate: 5.0000e-05 Epoch 43/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0025 - mae: 0.0158 - val_loss: 0.0059 - val_mae: 0.0459 - learning_rate: 5.0000e-05 Epoch 44/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0162 - val_loss: 0.0074 - val_mae: 0.0568 - learning_rate: 5.0000e-05 Epoch 45/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - loss: 0.0024 - mae: 0.0158 - val_loss: 0.0048 - val_mae: 0.0379 - learning_rate: 5.0000e-05 Epoch 46/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0026 - mae: 0.0159 - val_loss: 0.0069 - val_mae: 0.0541 - learning_rate: 5.0000e-05 Epoch 47/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0026 - mae: 0.0166 - val_loss: 0.0083 - val_mae: 0.0623 - learning_rate: 5.0000e-05 Epoch 48/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0167 - val_loss: 0.0068 - val_mae: 0.0527 - learning_rate: 5.0000e-05 Epoch 49/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0025 - mae: 0.0163 - val_loss: 0.0055 - val_mae: 0.0451 - learning_rate: 5.0000e-05 Epoch 50/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0156 - val_loss: 0.0076 - val_mae: 0.0587 - learning_rate: 5.0000e-05 Epoch 51/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0156 - val_loss: 0.0073 - val_mae: 0.0579 - learning_rate: 5.0000e-05 Epoch 52/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0162 - val_loss: 0.0074 - val_mae: 0.0588 - learning_rate: 5.0000e-05 Epoch 53/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0160 - val_loss: 0.0069 - val_mae: 0.0552 - learning_rate: 5.0000e-05 Epoch 54/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0023 - mae: 0.0159 - val_loss: 0.0060 - val_mae: 0.0493 - learning_rate: 5.0000e-05 Epoch 55/60 182/187 ━━━━━━━━━━━━━━━━━━━━ 0s 6ms/step - loss: 0.0024 - mae: 0.0165 Epoch 55: ReduceLROnPlateau reducing learning rate to 2.499999936844688e-05. 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0024 - mae: 0.0164 - val_loss: 0.0066 - val_mae: 0.0538 - learning_rate: 5.0000e-05 Epoch 56/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0156 - val_loss: 0.0055 - val_mae: 0.0468 - learning_rate: 2.5000e-05 Epoch 57/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 0.0020 - mae: 0.0152 - val_loss: 0.0059 - val_mae: 0.0491 - learning_rate: 2.5000e-05 Epoch 58/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0156 - val_loss: 0.0058 - val_mae: 0.0488 - learning_rate: 2.5000e-05 Epoch 59/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0021 - mae: 0.0158 - val_loss: 0.0068 - val_mae: 0.0559 - learning_rate: 2.5000e-05 Epoch 60/60 187/187 ━━━━━━━━━━━━━━━━━━━━ 1s 6ms/step - loss: 0.0020 - mae: 0.0150 - val_loss: 0.0086 - val_mae: 0.0657 - learning_rate: 2.5000e-05 Epoch 60: early stopping Restoring model weights from the end of the best epoch: 45.
Đánh giá mô hình 9:1¶
In [98]:
# Vẽ val_loss để đánh giá overfitting cho split 9:1
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2)
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2)
plt.title('RNN Model Loss 9:1 Split - Training vs Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Tìm epoch có val_loss thấp nhất cho split 9:1
best_epoch_91 = np.argmin(history_rnn_91.history['val_loss']) + 1
best_val_loss_91 = min(history_rnn_91.history['val_loss'])
print(f"Epoch tốt nhất (9:1): {best_epoch_91} với val_loss: {best_val_loss_91:.6f}")
Epoch tốt nhất (9:1): 45 với val_loss: 0.004782
Dự đoán và trực quan hóa 9:1¶
In [99]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho split 9:1
last_data_scaled_91 = scaled_test_input_91[-time_step:]
forecasted_prices_30_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 30, scaler_target)
forecasted_prices_60_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 60, scaler_target)
forecasted_prices_90_91 = forecast_multivariate_prices(model_rnn_91, last_data_scaled_91, time_step, 90, scaler_target)
# Dự đoán giá trên tập kiểm tra cho split 9:1
test_predict_scaled_91 = model_rnn_91.predict(X_test_91)
test_predict_rnn_91 = scaler_target.inverse_transform(test_predict_scaled_91)
# Tạo DataFrame cho các dự đoán 9:1
forecast_dates_30_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90_91 = pd.date_range(start=data.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
forecast_df_30_91 = pd.DataFrame(forecasted_prices_30_91, index=forecast_dates_30_91, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecasted_prices_60_91, index=forecast_dates_60_91, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecasted_prices_90_91, index=forecast_dates_90_91, columns=['Price'])
# Trực quan hóa kết quả cho split 9:1
plt.figure(figsize=(16, 10))
# Vẽ giá thực tế
plt.plot(data.index, data['Price'], label='Giá thực tế', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán trên tập test 9:1
plt.plot(test_data_91.index[time_step:], test_predict_rnn_91,
label='Dự đoán trên tập test (9:1)', color='orange', linewidth=2, alpha=0.8)
# Vẽ các dự đoán tương lai 9:1
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
label='Dự đoán 30 ngày tiếp theo (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
label='Dự đoán 60 ngày tiếp theo (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
label='Dự đoán 90 ngày tiếp theo (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Thêm đường thẳng đứng để phân biệt vùng dữ liệu
plt.axvline(x=data.index[-1], color='black', linestyle=':', alpha=0.7,
label='Ngày cuối cùng của dữ liệu thực tế')
plt.title(f'Dự đoán giá XRP bằng RNN (9:1 Split, Time Step = {time_step})\nDự đoán 30, 60, 90 ngày tiếp theo',
fontsize=16, fontweight='bold')
plt.xlabel('Ngày', fontsize=12)
plt.ylabel('Giá (USD)', fontsize=12)
plt.legend(fontsize=10, loc='upper left')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
9/9 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step
In [100]:
# Đánh giá mô hình 9:1
# Lấy giá trị thực tế trên tập test 9:1
y_test_actual_91 = test_data_91['Price'].values[time_step:]
# Tính toán các metrics cho split 9:1
mape_91 = mean_absolute_percentage_error(y_test_actual_91, test_predict_rnn_91.flatten())
mse_91 = mean_squared_error(y_test_actual_91, test_predict_rnn_91.flatten())
rmse_91 = np.sqrt(mse_91)
print(f'Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = {time_step}):')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'Số epochs huấn luyện: {len(history_rnn_91.history["loss"])}')
# Hiển thị thông tin dự đoán 30 ngày cho 9:1
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (9:1):')
print(f'Giá cao nhất: ${forecasted_prices_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecasted_prices_30_91.min():.2f}')
print(f'Giá trung bình: ${forecasted_prices_30_91.mean():.2f}')
Kết quả đánh giá mô hình RNN 9:1 Split (Time Step = 50): MAPE: 0.06% MSE: 0.03 RMSE: 0.18 Số epochs huấn luyện: 60 Dự đoán giá XRP 30 ngày tiếp theo (9:1): Giá cao nhất: $2.02 Giá thấp nhất: $1.80 Giá trung bình: $1.84
So sánh 3 tỉ lệ¶
In [101]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU")
print("="*80)
# Thu thập thông tin từ 3 splits
splits_info = {
'7:3': {
'train_size': len(train_data),
'test_size': len(test_data),
'mape': mape,
'mse': mse,
'rmse': rmse,
'epochs': len(history_rnn.history['loss']),
'best_val_loss': best_val_loss,
'best_epoch': best_epoch,
'final_train_loss': history_rnn.history['loss'][-1],
'final_val_loss': history_rnn.history['val_loss'][-1]
},
'8:2': {
'train_size': len(train_data_82),
'test_size': len(test_data_82),
'mape': mape_82,
'mse': mse_82,
'rmse': rmse_82,
'epochs': len(history_rnn_82.history['loss']),
'best_val_loss': best_val_loss_82,
'best_epoch': best_epoch_82,
'final_train_loss': history_rnn_82.history['loss'][-1],
'final_val_loss': history_rnn_82.history['val_loss'][-1]
},
'9:1': {
'train_size': len(train_data_91),
'test_size': len(test_data_91),
'mape': mape_91,
'mse': mse_91,
'rmse': rmse_91,
'epochs': len(history_rnn_91.history['loss']),
'best_val_loss': best_val_loss_91,
'best_epoch': best_epoch_91,
'final_train_loss': history_rnn_91.history['loss'][-1],
'final_val_loss': history_rnn_91.history['val_loss'][-1]
}
}
# In bảng so sánh
for split, info in splits_info.items():
print(f"\n{split} Split:")
print(f" Kích thước train: {info['train_size']:,} mẫu")
print(f" Kích thước test: {info['test_size']:,} mẫu")
print(f" MAPE: {info['mape']:.2f}%")
print(f" MSE: {info['mse']:,.2f}")
print(f" RMSE: {info['rmse']:,.2f}")
print(f" Số epochs: {info['epochs']}")
print(f" Best val_loss: {info['best_val_loss']:.6f} (epoch {info['best_epoch']})")
print(f" Final train_loss: {info['final_train_loss']:.6f}")
print(f" Final val_loss: {info['final_val_loss']:.6f}")
print(f" Overfitting gap: {abs(info['final_val_loss'] - info['final_train_loss']):.6f}")
================================================================================ SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU ================================================================================ 7:3 Split: Kích thước train: 2,358 mẫu Kích thước test: 1,011 mẫu MAPE: 0.04% MSE: 0.01 RMSE: 0.10 Số epochs: 60 Best val_loss: 0.002361 (epoch 57) Final train_loss: 0.001981 Final val_loss: 0.002595 Overfitting gap: 0.000614 8:2 Split: Kích thước train: 2,695 mẫu Kích thước test: 674 mẫu MAPE: 0.04% MSE: 0.01 RMSE: 0.10 Số epochs: 60 Best val_loss: 0.002149 (epoch 57) Final train_loss: 0.001851 Final val_loss: 0.002305 Overfitting gap: 0.000453 9:1 Split: Kích thước train: 3,032 mẫu Kích thước test: 337 mẫu MAPE: 0.06% MSE: 0.03 RMSE: 0.18 Số epochs: 60 Best val_loss: 0.004782 (epoch 45) Final train_loss: 0.002065 Final val_loss: 0.008559 Overfitting gap: 0.006494
In [102]:
# Vẽ biểu đồ so sánh các metrics
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
# 1. So sánh MAPE
mape_values = [splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 2. So sánh RMSE
rmse_values = [splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')
# 3. So sánh Best Validation Loss
best_val_loss_values = [splits_info[split]['best_val_loss'] for split in splits]
axes[0, 2].bar(splits, best_val_loss_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh Best Validation Loss', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('Best Val Loss')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(best_val_loss_values):
axes[0, 2].text(i, v + 0.0001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')
# 4. So sánh số epochs
epochs_values = [splits_info[split]['epochs'] for split in splits]
axes[1, 0].bar(splits, epochs_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh Số Epochs', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('Số Epochs')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(epochs_values):
axes[1, 0].text(i, v + 0.5, f'{v}', ha='center', va='bottom', fontweight='bold')
# 5. So sánh Overfitting Gap
overfitting_gap = [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits]
axes[1, 1].bar(splits, overfitting_gap, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Overfitting Gap', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('|Val Loss - Train Loss|')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(overfitting_gap):
axes[1, 1].text(i, v + 0.001, f'{v:.4f}', ha='center', va='bottom', fontweight='bold')
# 6. So sánh kích thước test set
test_sizes = [splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')
plt.tight_layout()
plt.show()
C:\Users\Hii\AppData\Local\Temp\ipykernel_28116\2283599594.py:62: UserWarning: Tight layout not applied. The bottom and top margins cannot be made large enough to accommodate all Axes decorations. plt.tight_layout()
In [103]:
# Vẽ so sánh Training và Validation Loss curves
plt.figure(figsize=(18, 6))
# Subplot 1: 7:3 Split
plt.subplot(1, 3, 1)
plt.plot(history_rnn.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('7:3 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch}')
# Subplot 2: 8:2 Split
plt.subplot(1, 3, 2)
plt.plot(history_rnn_82.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_82.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('8:2 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_82-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_82}')
# Subplot 3: 9:1 Split
plt.subplot(1, 3, 3)
plt.plot(history_rnn_91.history['loss'], label='Training Loss', linewidth=2, color='blue')
plt.plot(history_rnn_91.history['val_loss'], label='Validation Loss', linewidth=2, color='red')
plt.title('9:1 Split - Loss Curves', fontsize=14, fontweight='bold')
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=best_epoch_91-1, color='green', linestyle='--', alpha=0.7, label=f'Best Epoch: {best_epoch_91}')
plt.tight_layout()
plt.show()
In [104]:
# Tạo DataFrame tổng hợp kết quả để dễ so sánh
import pandas as pd
comparison_df = pd.DataFrame({
'Split': ['7:3', '8:2', '9:1'],
'Train_Size': [splits_info[split]['train_size'] for split in splits],
'Test_Size': [splits_info[split]['test_size'] for split in splits],
'MAPE (%)': [splits_info[split]['mape'] for split in splits],
'RMSE (USD)': [splits_info[split]['rmse'] for split in splits],
'MSE': [splits_info[split]['mse'] for split in splits],
'Best_Val_Loss': [splits_info[split]['best_val_loss'] for split in splits],
'Best_Epoch': [splits_info[split]['best_epoch'] for split in splits],
'Total_Epochs': [splits_info[split]['epochs'] for split in splits],
'Overfitting_Gap': [abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits]
})
print("\nBẢNG TỔNG HỢP KẾT QUẢ:")
print("="*100)
print(comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ: ==================================================================================================== Split Train_Size Test_Size MAPE (%) RMSE (USD) MSE Best_Val_Loss Best_Epoch Total_Epochs Overfitting_Gap 7:3 2358 1011 0.0423 0.1026 0.0105 0.0024 57 60 0.0006 8:2 2695 674 0.0421 0.0995 0.0099 0.0021 57 60 0.0005 9:1 3032 337 0.0614 0.1795 0.0322 0.0048 45 60 0.0065
In [105]:
# Phân tích và đưa ra khuyến nghị
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ")
print("="*80)
# Tìm split tốt nhất cho từng metric
best_mape_split = splits[np.argmin([splits_info[split]['mape'] for split in splits])]
best_rmse_split = splits[np.argmin([splits_info[split]['rmse'] for split in splits])]
best_val_loss_split = splits[np.argmin([splits_info[split]['best_val_loss'] for split in splits])]
best_overfitting_split = splits[np.argmin([abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss'])
for split in splits])]
print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:")
print(f" • Tốt nhất theo MAPE: {best_mape_split} ({splits_info[best_mape_split]['mape']:.2f}%)")
print(f" • Tốt nhất theo RMSE: {best_rmse_split} ({splits_info[best_rmse_split]['rmse']:,.2f} USD)")
print(f" • Tốt nhất theo Val Loss: {best_val_loss_split} ({splits_info[best_val_loss_split]['best_val_loss']:.6f})")
print(f" • Ít overfitting nhất: {best_overfitting_split} (gap: {abs(splits_info[best_overfitting_split]['final_val_loss'] - splits_info[best_overfitting_split]['final_train_loss']):.6f})")
# Tính điểm tổng hợp (rank-based scoring)
def calculate_rank_score(splits_info):
scores = {}
splits_list = list(splits_info.keys())
# Rank cho MAPE (thấp hơn = tốt hơn)
mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
# Rank cho RMSE (thấp hơn = tốt hơn)
rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
# Rank cho Best Val Loss (thấp hơn = tốt hơn)
val_loss_rank = sorted(splits_list, key=lambda x: splits_info[x]['best_val_loss'])
# Rank cho Overfitting Gap (thấp hơn = tốt hơn)
overfitting_rank = sorted(splits_list, key=lambda x: abs(splits_info[x]['final_val_loss'] - splits_info[x]['final_train_loss']))
for split in splits_list:
# Điểm rank (1 = tốt nhất, 3 = kém nhất)
score = (mape_rank.index(split) + 1) * 0.3 + \
(rmse_rank.index(split) + 1) * 0.3 + \
(val_loss_rank.index(split) + 1) * 0.25 + \
(overfitting_rank.index(split) + 1) * 0.15
scores[split] = score
return scores
# Tính điểm tổng hợp
scores = calculate_rank_score(splits_info)
best_overall_split = min(scores, key=scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):")
for split in splits_info.keys():
print(f" • {split}: {scores[split]:.2f} điểm")
print(f" • {split}: {scores[split]:.2f} điểm")
print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ:")
print(f" 🏆 MÔ HÌNH TỐT NHẤT: Split {best_overall_split}")
print(f" 📊 Lý do:")
print(f" - MAPE: {splits_info[best_overall_split]['mape']:.2f}%")
print(f" - RMSE: {splits_info[best_overall_split]['rmse']:,.2f} USD")
print(f" - Best Val Loss: {splits_info[best_overall_split]['best_val_loss']:.6f}")
print(f" - Overfitting Gap: {abs(splits_info[best_overall_split]['final_val_loss'] - splits_info[best_overall_split]['final_train_loss']):.6f}")
print(f" - Tập test có {splits_info[best_overall_split]['test_size']:,} mẫu (đủ để đánh giá)")
print(f" - Huấn luyện ổn định với {splits_info[best_overall_split]['epochs']} epochs")
print(f"\n4. NHẬN XÉT CHUNG:")
if best_overall_split == '7:3':
print(" • Split 7:3 cân bằng tốt giữa kích thước tập train và test")
print(" • Tập test đủ lớn để đánh giá độ tin cậy của mô hình")
print(" • Hiệu suất dự đoán tốt với mức overfitting chấp nhận được")
elif best_overall_split == '8:2':
print(" • Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn")
print(" • Tập test vẫn đủ lớn để đánh giá")
print(" • Cân bằng tốt giữa hiệu suất và độ tin cậy")
else: # 9:1
print(" • Split 9:1 tối đa hóa dữ liệu train")
print(" • Có thể có rủi ro về độ tin cậy do tập test nhỏ")
print(" • Phù hợp khi có ít dữ liệu và cần tối ưu hiệu suất")
print(f"\n ⚠️ LƯU Ý: Với dữ liệu time series như XRP, nên chọn split cân bằng")
print(f" để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ
================================================================================
1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ:
• Tốt nhất theo MAPE: 8:2 (0.04%)
• Tốt nhất theo RMSE: 8:2 (0.10 USD)
• Tốt nhất theo Val Loss: 8:2 (0.002149)
• Ít overfitting nhất: 8:2 (gap: 0.000453)
2. ĐIỂM TỔNG HỢP (trọng số: MAPE=30%, RMSE=30%, Val_Loss=25%, Overfitting=15%):
• 7:3: 2.00 điểm
• 7:3: 2.00 điểm
• 8:2: 1.00 điểm
• 8:2: 1.00 điểm
• 9:1: 3.00 điểm
• 9:1: 3.00 điểm
3. KẾT LUẬN VÀ KHUYẾN NGHỊ:
🏆 MÔ HÌNH TỐT NHẤT: Split 8:2
📊 Lý do:
- MAPE: 0.04%
- RMSE: 0.10 USD
- Best Val Loss: 0.002149
- Overfitting Gap: 0.000453
- Tập test có 674 mẫu (đủ để đánh giá)
- Huấn luyện ổn định với 60 epochs
4. NHẬN XÉT CHUNG:
• Split 8:2 có nhiều dữ liệu train hơn, giúp mô hình học tốt hơn
• Tập test vẫn đủ lớn để đánh giá
• Cân bằng tốt giữa hiệu suất và độ tin cậy
⚠️ LƯU Ý: Với dữ liệu time series như XRP, nên chọn split cân bằng
để đảm bảo tập test đủ lớn và đại diện cho nhiều giai đoạn thị trường khác nhau.
In [106]:
# Vẽ biểu đồ radar cho so sánh tổng thể
import numpy as np
import matplotlib.pyplot as plt
def create_radar_chart():
# Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
metrics = ['MAPE', 'RMSE', 'Val_Loss', 'Overfitting', 'Test_Size']
# Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
data = {}
for split in splits:
mape_norm = 1 - (splits_info[split]['mape'] - min([splits_info[s]['mape'] for s in splits])) / \
(max([splits_info[s]['mape'] for s in splits]) - min([splits_info[s]['mape'] for s in splits]))
rmse_norm = 1 - (splits_info[split]['rmse'] - min([splits_info[s]['rmse'] for s in splits])) / \
(max([splits_info[s]['rmse'] for s in splits]) - min([splits_info[s]['rmse'] for s in splits]))
val_loss_norm = 1 - (splits_info[split]['best_val_loss'] - min([splits_info[s]['best_val_loss'] for s in splits])) / \
(max([splits_info[s]['best_val_loss'] for s in splits]) - min([splits_info[s]['best_val_loss'] for s in splits]))
overfitting_gaps = [abs(splits_info[s]['final_val_loss'] - splits_info[s]['final_train_loss']) for s in splits]
overfitting_norm = 1 - (abs(splits_info[split]['final_val_loss'] - splits_info[split]['final_train_loss']) - min(overfitting_gaps)) / \
(max(overfitting_gaps) - min(overfitting_gaps))
test_size_norm = (splits_info[split]['test_size'] - min([splits_info[s]['test_size'] for s in splits])) / \
(max([splits_info[s]['test_size'] for s in splits]) - min([splits_info[s]['test_size'] for s in splits]))
data[split] = [mape_norm, rmse_norm, val_loss_norm, overfitting_norm, test_size_norm]
# Tạo radar chart
angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
angles += angles[:1] # Complete the circle
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
for i, (split, values) in enumerate(data.items()):
values += values[:1] # Complete the circle
ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
ax.fill(angles, values, alpha=0.25, color=colors[i])
ax.set_xticks(angles[:-1])
ax.set_xticklabels(metrics)
ax.set_ylim(0, 1)
ax.set_title('So sánh tổng thể các Split (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
ax.grid(True)
plt.tight_layout()
plt.show()
create_radar_chart()
Kết luận cuối cùng¶
Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình RNN với split tỉ lệ dữ liệu tốt nhất đã được xác định.
Các yếu tố được xem xét:
- MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán
- RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
- Validation Loss: Đo lường hiệu suất trên tập validation
- Overfitting Gap: Đo lường mức độ overfitting
- Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá
Khuyến nghị sử dụng: Mô hình với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá XRP.